From 92d4251090865cf23cc0fae9fdf1d57244d34fa9 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Fri, 3 Sep 2004 17:24:05 +0200 Subject: [PATCH] USB: update of help text for hpusbscsi Signed-Off-By: Oliver Neukum Signed-off-by: Greg Kroah-Hartman --- drivers/usb/image/Kconfig | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/usb/image/Kconfig b/drivers/usb/image/Kconfig index 0b0f80e4f9a7..b541b67d2eb6 100644 --- a/drivers/usb/image/Kconfig +++ b/drivers/usb/image/Kconfig @@ -30,11 +30,12 @@ config USB_MICROTEK This driver can be compiled as a module, called microtek. config USB_HPUSBSCSI - tristate "HP53xx USB scanner support (EXPERIMENTAL)" - depends on USB && SCSI && EXPERIMENTAL + tristate "HP53xx USB scanner support" + depends on USB && SCSI help Say Y here if you want support for the HP 53xx series of scanners - and the Minolta Scan Dual. This driver is experimental. + and the Minolta Scan Dual. The scanner will be accessible as a SCSI device. + Please note that recent versions of SANE use usbfs, not this driver. This can be compiled as a module, called hpusbscsi. -- cgit v1.2.3 From ba6cf9f4aefe91625b75b3970be06194b723568b Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Fri, 3 Sep 2004 17:24:41 +0200 Subject: [PATCH] USB: switching microtek to usb_kill_urb this reworks the microtek driver's disconnect method, now that usb_kill_urb is available. Signed-Off-By: Oliver Neukum Signed-off-by: Greg Kroah-Hartman --- drivers/usb/image/microtek.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/usb/image/microtek.c b/drivers/usb/image/microtek.c index 9d3aa3075086..c54aa114a51e 100644 --- a/drivers/usb/image/microtek.c +++ b/drivers/usb/image/microtek.c @@ -324,7 +324,7 @@ static inline void mts_urb_abort(struct mts_desc* desc) { MTS_DEBUG_GOT_HERE(); mts_debug_dump(desc); - usb_unlink_urb( desc->urb ); + usb_kill_urb( desc->urb ); } static int mts_scsi_abort (Scsi_Cmnd *srb) @@ -822,9 +822,8 @@ static void mts_usb_disconnect (struct usb_interface *intf) usb_set_intfdata(intf, NULL); + usb_kill_urb(desc->urb); scsi_remove_host(desc->host); - usb_unlink_urb(desc->urb); - scsi_host_put(desc->host); usb_free_urb(desc->urb); kfree(desc); -- cgit v1.2.3 From 98b069b9e50c88105ef9e7fa134554ae0cdcd044 Mon Sep 17 00:00:00 2001 From: Pete Zaitcev Date: Fri, 3 Sep 2004 17:25:14 +0200 Subject: [PATCH] USB: Fixes for ub in 2.4.9-rc1 from Oliver and Pat - Set the allocation size in REQUEST SENSE (Pat LaVarre) - Move add_timer invocations to safer places (Oliver Neukum) Signed-off-by: Greg Kroah-Hartman --- drivers/block/ub.c | 59 +++++++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/drivers/block/ub.c b/drivers/block/ub.c index f605535d3f56..6379b9043631 100644 --- a/drivers/block/ub.c +++ b/drivers/block/ub.c @@ -786,17 +786,16 @@ static int ub_scsi_cmd_start(struct ub_dev *sc, struct ub_scsi_cmd *cmd) sc->work_urb.error_count = 0; sc->work_urb.status = 0; - sc->work_timer.expires = jiffies + UB_URB_TIMEOUT; - add_timer(&sc->work_timer); - if ((rc = usb_submit_urb(&sc->work_urb, GFP_ATOMIC)) != 0) { /* XXX Clear stalls */ printk("ub: cmd #%d start failed (%d)\n", cmd->tag, rc); /* P3 */ - del_timer(&sc->work_timer); ub_complete(&sc->work_done); return rc; } + sc->work_timer.expires = jiffies + UB_URB_TIMEOUT; + add_timer(&sc->work_timer); + cmd->state = UB_CMDST_CMD; ub_cmdtr_state(sc, cmd); return 0; @@ -968,18 +967,17 @@ static void ub_scsi_urb_compl(struct ub_dev *sc, struct ub_scsi_cmd *cmd) sc->work_urb.error_count = 0; sc->work_urb.status = 0; - sc->work_timer.expires = jiffies + UB_URB_TIMEOUT; - add_timer(&sc->work_timer); - if ((rc = usb_submit_urb(&sc->work_urb, GFP_ATOMIC)) != 0) { /* XXX Clear stalls */ printk("ub: data #%d submit failed (%d)\n", cmd->tag, rc); /* P3 */ - del_timer(&sc->work_timer); ub_complete(&sc->work_done); ub_state_done(sc, cmd, rc); return; } + sc->work_timer.expires = jiffies + UB_URB_TIMEOUT; + add_timer(&sc->work_timer); + cmd->state = UB_CMDST_DATA; ub_cmdtr_state(sc, cmd); @@ -1063,19 +1061,18 @@ static void ub_scsi_urb_compl(struct ub_dev *sc, struct ub_scsi_cmd *cmd) sc->work_urb.error_count = 0; sc->work_urb.status = 0; - sc->work_timer.expires = jiffies + UB_URB_TIMEOUT; - add_timer(&sc->work_timer); - rc = usb_submit_urb(&sc->work_urb, GFP_ATOMIC); if (rc != 0) { /* XXX Clear stalls */ printk("%s: CSW #%d submit failed (%d)\n", sc->name, cmd->tag, rc); /* P3 */ - del_timer(&sc->work_timer); ub_complete(&sc->work_done); ub_state_done(sc, cmd, rc); return; } + + sc->work_timer.expires = jiffies + UB_URB_TIMEOUT; + add_timer(&sc->work_timer); return; } @@ -1186,18 +1183,17 @@ static void ub_state_stat(struct ub_dev *sc, struct ub_scsi_cmd *cmd) sc->work_urb.error_count = 0; sc->work_urb.status = 0; - sc->work_timer.expires = jiffies + UB_URB_TIMEOUT; - add_timer(&sc->work_timer); - if ((rc = usb_submit_urb(&sc->work_urb, GFP_ATOMIC)) != 0) { /* XXX Clear stalls */ printk("ub: CSW #%d submit failed (%d)\n", cmd->tag, rc); /* P3 */ - del_timer(&sc->work_timer); ub_complete(&sc->work_done); ub_state_done(sc, cmd, rc); return; } + sc->work_timer.expires = jiffies + UB_URB_TIMEOUT; + add_timer(&sc->work_timer); + cmd->stat_count = 0; cmd->state = UB_CMDST_STAT; ub_cmdtr_state(sc, cmd); @@ -1217,9 +1213,17 @@ static void ub_state_sense(struct ub_dev *sc, struct ub_scsi_cmd *cmd) goto error; } + /* + * ``If the allocation length is eighteen or greater, and a device + * server returns less than eithteen bytes of data, the application + * client should assume that the bytes not transferred would have been + * zeroes had the device server returned those bytes.'' + */ memset(&sc->top_sense, 0, UB_SENSE_SIZE); + scmd = &sc->top_rqs_cmd; scmd->cdb[0] = REQUEST_SENSE; + scmd->cdb[4] = UB_SENSE_SIZE; scmd->cdb_len = 6; scmd->dir = UB_DIR_READ; scmd->state = UB_CMDST_INIT; @@ -1271,14 +1275,13 @@ static int ub_submit_clear_stall(struct ub_dev *sc, struct ub_scsi_cmd *cmd, sc->work_urb.error_count = 0; sc->work_urb.status = 0; - sc->work_timer.expires = jiffies + UB_CTRL_TIMEOUT; - add_timer(&sc->work_timer); - if ((rc = usb_submit_urb(&sc->work_urb, GFP_ATOMIC)) != 0) { - del_timer(&sc->work_timer); ub_complete(&sc->work_done); return rc; } + + sc->work_timer.expires = jiffies + UB_CTRL_TIMEOUT; + add_timer(&sc->work_timer); return 0; } @@ -1289,6 +1292,9 @@ static void ub_top_sense_done(struct ub_dev *sc, struct ub_scsi_cmd *scmd) unsigned char *sense = scmd->data; struct ub_scsi_cmd *cmd; + /* + * Ignoring scmd->act_len, because the buffer was pre-zeroed. + */ ub_cmdtr_sense(sc, scmd, sense); if ((cmd = ub_cmdq_peek(sc)) == NULL) { @@ -1725,19 +1731,18 @@ static int ub_probe_clear_stall(struct ub_dev *sc, int stalled_pipe) sc->work_urb.error_count = 0; sc->work_urb.status = 0; - init_timer(&timer); - timer.function = ub_probe_timeout; - timer.data = (unsigned long) &compl; - timer.expires = jiffies + UB_CTRL_TIMEOUT; - add_timer(&timer); - if ((rc = usb_submit_urb(&sc->work_urb, GFP_KERNEL)) != 0) { printk(KERN_WARNING "%s: Unable to submit a probe clear (%d)\n", sc->name, rc); - del_timer_sync(&timer); return rc; } + init_timer(&timer); + timer.function = ub_probe_timeout; + timer.data = (unsigned long) &compl; + timer.expires = jiffies + UB_CTRL_TIMEOUT; + add_timer(&timer); + wait_for_completion(&compl); del_timer_sync(&timer); -- cgit v1.2.3 From 9e0aea63d90fc678e3d2ed5c038d657e4fabaa2c Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Fri, 3 Sep 2004 17:25:50 +0200 Subject: [PATCH] USB: Make usbcore use usb_kill_urb() This patch changes the only places in usbcore where usb_unlink_urb() is still used for synchronous unlinking; now they will use usb_kill_urb(). As it turns out, there were only a couple of changes needed. This still leaves all the drivers to audit! Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/devio.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index 776c1bf0df9b..17ca7690dbde 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -286,9 +286,10 @@ static void destroy_async (struct dev_state *ps, struct list_head *list) while (!list_empty(list)) { as = list_entry(list->next, struct async, asynclist); list_del_init(&as->asynclist); + + /* drop the spinlock so the completion handler can run */ spin_unlock_irqrestore(&ps->lock, flags); - /* usb_unlink_urb calls the completion handler with status == -ENOENT */ - usb_unlink_urb(as->urb); + usb_kill_urb(as->urb); spin_lock_irqsave(&ps->lock, flags); } spin_unlock_irqrestore(&ps->lock, flags); @@ -976,7 +977,7 @@ static int proc_unlinkurb(struct dev_state *ps, void __user *arg) as = async_getpending(ps, arg); if (!as) return -EINVAL; - usb_unlink_urb(as->urb); + usb_kill_urb(as->urb); return 0; } -- cgit v1.2.3 From 465f27945df776ee9b640bc02380db7b595f7209 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Fri, 3 Sep 2004 17:26:21 +0200 Subject: [PATCH] USB: Suspend/resume/wakeup support for UHCI root hub ports This patch adds support for suspending, resuming, and remote wakeup detection on root hub ports to the UHCI driver. It doesn't add support for suspending or resuming the root hub itself (beyond what's already there) -- that will require considerably more work. But at least devices plugged directly into the computer will interact nicely with power management. Of lesser importance, the patch also simplifies some constant expressions. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/uhci-hcd.c | 2 + drivers/usb/host/uhci-hcd.h | 6 +++ drivers/usb/host/uhci-hub.c | 91 ++++++++++++++++++++++++++++++++------------- 3 files changed, 73 insertions(+), 26 deletions(-) diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c index 00e6eb15a6ce..fe924e65ba35 100644 --- a/drivers/usb/host/uhci-hcd.c +++ b/drivers/usb/host/uhci-hcd.c @@ -1661,6 +1661,8 @@ static void stall_callback(unsigned long ptr) /* Poll for and perform state transitions */ hc_state_transitions(uhci); + if (unlikely(uhci->suspended_ports && uhci->state != UHCI_SUSPENDED)) + uhci_check_resume(uhci); init_stall_timer(hcd); } diff --git a/drivers/usb/host/uhci-hcd.h b/drivers/usb/host/uhci-hcd.h index b4bf3b6cac38..3a6735740364 100644 --- a/drivers/usb/host/uhci-hcd.h +++ b/drivers/usb/host/uhci-hcd.h @@ -352,6 +352,12 @@ struct uhci_hcd { int resume_detect; /* Need a Global Resume */ unsigned int saved_framenumber; /* Save during PM suspend */ + /* Support for port suspend/resume */ + unsigned long port_c_suspend; /* Bit-arrays of ports */ + unsigned long suspended_ports; + unsigned long resuming_ports; + unsigned long resume_timeout; /* Time to stop signalling */ + /* Main list of URB's currently controlled by this HC */ struct list_head urb_list; /* P: uhci->schedule_lock */ diff --git a/drivers/usb/host/uhci-hub.c b/drivers/usb/host/uhci-hub.c index 2dde381632e9..e869ad4061f1 100644 --- a/drivers/usb/host/uhci-hub.c +++ b/drivers/usb/host/uhci-hub.c @@ -36,13 +36,13 @@ static __u8 root_hub_hub_des[] = static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf) { struct uhci_hcd *uhci = hcd_to_uhci(hcd); - unsigned long io_addr = uhci->io_addr; - int i; + int port; *buf = 0; - for (i = 0; i < uhci->rh_numports; i++) { - if (inw(io_addr + USBPORTSC1 + i * 2) & RWC_BITS) - *buf |= (1 << (i + 1)); + for (port = 0; port < uhci->rh_numports; ++port) { + if ((inw(uhci->io_addr + USBPORTSC1 + port * 2) & RWC_BITS) || + test_bit(port, &uhci->port_c_suspend)) + *buf |= (1 << (port + 1)); } return !!*buf; } @@ -62,6 +62,35 @@ static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf) status &= ~(RWC_BITS|WZ_BITS); \ outw(status, port_addr) +/* UHCI controllers don't automatically stop resume signalling after 20 msec, + * so we have to poll and check timeouts in order to take care of it. + * FIXME: Synchronize access to these fields by a spinlock. + */ +static void uhci_check_resume(struct uhci_hcd *uhci) +{ + unsigned int port; + unsigned int port_addr; + int status; + + for (port = 0; port < uhci->rh_numports; ++port) { + port_addr = uhci->io_addr + USBPORTSC1 + 2 * port; + if (unlikely(inw(port_addr) & USBPORTSC_RD)) { + if (!test_bit(port, &uhci->resuming_ports)) { + + /* Port received a wakeup request */ + set_bit(port, &uhci->resuming_ports); + uhci->resume_timeout = jiffies + + msecs_to_jiffies(20); + } else if (time_after_eq(jiffies, + uhci->resume_timeout)) { + CLR_RH_PORTSTAT(USBPORTSC_SUSP | USBPORTSC_RD); + clear_bit(port, &uhci->resuming_ports); + clear_bit(port, &uhci->suspended_ports); + set_bit(port, &uhci->port_c_suspend); + } + } + } +} /* size of returned buffer is part of USB spec */ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, @@ -69,8 +98,9 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, { struct uhci_hcd *uhci = hcd_to_uhci(hcd); int status, retval = 0, len = 0; - unsigned long port_addr = uhci->io_addr + USBPORTSC1 + 2 * (wIndex-1); - __u16 wPortChange, wPortStatus; + unsigned int port = wIndex - 1; + unsigned long port_addr = uhci->io_addr + USBPORTSC1 + 2 * port; + u16 wPortChange, wPortStatus; switch (typeReq) { /* Request Destination: @@ -82,11 +112,15 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, */ case GetHubStatus: - *(__u32 *)buf = cpu_to_le32(0); + *(u32 *) buf = cpu_to_le32(0); OK(4); /* hub power */ case GetPortStatus: - if (!wIndex || wIndex > uhci->rh_numports) + if (port >= uhci->rh_numports) goto err; + + if (uhci->resuming_ports) + uhci_check_resume(uhci); + status = inw(port_addr); /* Intel controllers report the OverCurrent bit active on. @@ -97,37 +131,39 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, PCI_VENDOR_ID_VIA) status ^= USBPORTSC_OC; - /* UHCI doesn't support C_SUSPEND and C_RESET (always false) */ + /* UHCI doesn't support C_RESET (always false) */ wPortChange = 0; if (status & USBPORTSC_CSC) - wPortChange |= 1 << (USB_PORT_FEAT_C_CONNECTION - 16); + wPortChange |= USB_PORT_STAT_C_CONNECTION; if (status & USBPORTSC_PEC) - wPortChange |= 1 << (USB_PORT_FEAT_C_ENABLE - 16); + wPortChange |= USB_PORT_STAT_C_ENABLE; if (status & USBPORTSC_OCC) - wPortChange |= 1 << (USB_PORT_FEAT_C_OVER_CURRENT - 16); + wPortChange |= USB_PORT_STAT_C_OVERCURRENT; + if (test_bit(port, &uhci->port_c_suspend)) + wPortChange |= USB_PORT_STAT_C_SUSPEND; /* UHCI has no power switching (always on) */ - wPortStatus = 1 << USB_PORT_FEAT_POWER; + wPortStatus = USB_PORT_STAT_POWER; if (status & USBPORTSC_CCS) - wPortStatus |= 1 << USB_PORT_FEAT_CONNECTION; + wPortStatus |= USB_PORT_STAT_CONNECTION; if (status & USBPORTSC_PE) { - wPortStatus |= 1 << USB_PORT_FEAT_ENABLE; + wPortStatus |= USB_PORT_STAT_ENABLE; if (status & (USBPORTSC_SUSP | USBPORTSC_RD)) - wPortStatus |= 1 << USB_PORT_FEAT_SUSPEND; + wPortStatus |= USB_PORT_STAT_SUSPEND; } if (status & USBPORTSC_OC) - wPortStatus |= 1 << USB_PORT_FEAT_OVER_CURRENT; + wPortStatus |= USB_PORT_STAT_OVERCURRENT; if (status & USBPORTSC_PR) - wPortStatus |= 1 << USB_PORT_FEAT_RESET; + wPortStatus |= USB_PORT_STAT_RESET; if (status & USBPORTSC_LSDA) - wPortStatus |= 1 << USB_PORT_FEAT_LOWSPEED; + wPortStatus |= USB_PORT_STAT_LOW_SPEED; if (wPortChange) dev_dbg(uhci_dev(uhci), "port %d portsc %04x\n", wIndex, status); - *(__u16 *)buf = cpu_to_le16(wPortStatus); - *(__u16 *)(buf + 2) = cpu_to_le16(wPortChange); + *(u16 *) buf = cpu_to_le16(wPortStatus); + *(u16 *) (buf + 2) = cpu_to_le16(wPortChange); OK(4); case SetHubFeature: /* We don't implement these */ case ClearHubFeature: @@ -140,11 +176,12 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, } break; case SetPortFeature: - if (!wIndex || wIndex > uhci->rh_numports) + if (port >= uhci->rh_numports) goto err; switch (wValue) { case USB_PORT_FEAT_SUSPEND: + set_bit(port, &uhci->suspended_ports); SET_RH_PORTSTAT(USBPORTSC_SUSP); OK(0); case USB_PORT_FEAT_RESET: @@ -164,7 +201,7 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, } break; case ClearPortFeature: - if (!wIndex || wIndex > uhci->rh_numports) + if (port >= uhci->rh_numports) goto err; switch (wValue) { @@ -175,10 +212,12 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, CLR_RH_PORTSTAT(USBPORTSC_PEC); OK(0); case USB_PORT_FEAT_SUSPEND: - CLR_RH_PORTSTAT(USBPORTSC_SUSP); + set_bit(port, &uhci->resuming_ports); + uhci->resume_timeout = jiffies + msecs_to_jiffies(20); + SET_RH_PORTSTAT(USBPORTSC_RD); OK(0); case USB_PORT_FEAT_C_SUSPEND: - /* this driver won't report these */ + clear_bit(port, &uhci->port_c_suspend); OK(0); case USB_PORT_FEAT_POWER: /* UHCI has no power switching */ -- cgit v1.2.3 From cab5916ae0e171fb74728e1f1bdcfa06c6957da9 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Fri, 3 Sep 2004 17:26:56 +0200 Subject: [PATCH] USB: correct interrupt interval for kaweth - overriding the media check interrupt interval Signed-Off-By: Oliver Neukum Signed-off-by: Greg Kroah-Hartman --- drivers/usb/net/kaweth.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/net/kaweth.c b/drivers/usb/net/kaweth.c index c366fe957060..a84569a43c39 100644 --- a/drivers/usb/net/kaweth.c +++ b/drivers/usb/net/kaweth.c @@ -668,7 +668,7 @@ static int kaweth_open(struct net_device *net) INTBUFFERSIZE, int_callback, kaweth, - 8); + 250); /* overriding the descriptor */ kaweth->irq_urb->transfer_dma = kaweth->intbufferhandle; kaweth->irq_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; -- cgit v1.2.3 From 5e2522ee43db9ae51e89b24c8c5d3a0192f2943a Mon Sep 17 00:00:00 2001 From: Wouter Van Hemel Date: Fri, 3 Sep 2004 17:27:27 +0200 Subject: [PATCH] USB: usb audio is for oss only Doh. Fixed. --- drivers/usb/class/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/class/Kconfig b/drivers/usb/class/Kconfig index 3d49e9d8cb67..0561d0234f23 100644 --- a/drivers/usb/class/Kconfig +++ b/drivers/usb/class/Kconfig @@ -9,7 +9,8 @@ config USB_AUDIO depends on USB && SOUND help Say Y here if you want to connect USB audio equipment such as - speakers to your computer's USB port. + speakers to your computer's USB port. You only need this if you use + the OSS sound driver; ALSA has its own option for usb audio support. To compile this driver as a module, choose M here: the module will be called audio. -- cgit v1.2.3 From f388f977d2ef1a9f0c2e2a5e268ff8d390819c68 Mon Sep 17 00:00:00 2001 From: Petko Manolov Date: Fri, 3 Sep 2004 17:28:02 +0200 Subject: [PATCH] USB: small rtl8150 patch this one make rtl8150 auto-load its register values at reset. Not doing so is known to cause improper setup when the device is being reseted frequently. Signed-off-by: Greg Kroah-Hartman --- drivers/usb/net/rtl8150.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/net/rtl8150.c b/drivers/usb/net/rtl8150.c index 6724399513f3..c7daf82e3a34 100644 --- a/drivers/usb/net/rtl8150.c +++ b/drivers/usb/net/rtl8150.c @@ -20,7 +20,7 @@ #include /* Version Information */ -#define DRIVER_VERSION "v0.6.1 (2004/03/13)" +#define DRIVER_VERSION "v0.6.2 (2004/08/27)" #define DRIVER_AUTHOR "Petko Manolov " #define DRIVER_DESC "rtl8150 based usb-ethernet driver" @@ -344,7 +344,7 @@ static int rtl8150_set_mac_address(struct net_device *netdev, void *p) static int rtl8150_reset(rtl8150_t * dev) { - u8 data = 0x10; + u8 data = 0x11; int i = HZ; set_registers(dev, CR, 1, &data); -- cgit v1.2.3 From d4fcfc90ed7621d072e88e6e18f9ed84692a26cf Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Fri, 3 Sep 2004 17:28:34 +0200 Subject: [PATCH] USB: switching microtek to usb_kill_urb Am Dienstag, 31. August 2004 17:41 schrieb Alan Stern: > On Tue, 31 Aug 2004, Oliver Neukum wrote: > > Alan Stern > > > > > > @@ -822,9 +822,8 @@ > > > > usb_set_intfdata(intf, NULL); > > > > + usb_kill_urb(desc->urb); > > scsi_remove_host(desc->host); > > - usb_unlink_urb(desc->urb); > > - scsi_host_put(desc->host); > > > > usb_free_urb(desc->urb); > > kfree(desc); > > I think you still need the scsi_host_put(), to account for the fact that > scsi_host_alloc() sets the refcount to 1 initially. Right you are. Greg, please also apply this one. Signed-Off-By: Oliver Neukum Signed-off-by: Greg Kroah-Hartman --- drivers/usb/image/microtek.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/image/microtek.c b/drivers/usb/image/microtek.c index c54aa114a51e..14b218239a8c 100644 --- a/drivers/usb/image/microtek.c +++ b/drivers/usb/image/microtek.c @@ -825,6 +825,7 @@ static void mts_usb_disconnect (struct usb_interface *intf) usb_kill_urb(desc->urb); scsi_remove_host(desc->host); + scsi_host_put(desc->host); usb_free_urb(desc->urb); kfree(desc); } -- cgit v1.2.3 From f6799f05b11b2709a21300cfe34de5612c7ba3a1 Mon Sep 17 00:00:00 2001 From: Al Borchers Date: Fri, 3 Sep 2004 17:29:11 +0200 Subject: [PATCH] USB: corrected digi_acceleport 2.6.9-rc1 fix for hang on disconnect - Fixed hang on disconnect in digi_acceleport USB serial driver. See http://bugme.osdl.org/show_bug.cgi?id=2459. Close after disconnect no longer tries to communicate with the device. Signed-off-by: Al Borchers Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/digi_acceleport.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index f187b3165d57..6d01337eefc0 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -1553,13 +1553,17 @@ static void digi_close( struct usb_serial_port *port, struct file *filp ) dbg( "digi_close: TOP: port=%d, open_count=%d", priv->dp_port_num, port->open_count ); + /* if disconnected, just clear flags */ + if (!usb_get_intfdata(port->serial->interface)) + goto exit; + /* do cleanup only after final close on this port */ spin_lock_irqsave( &priv->dp_port_lock, flags ); priv->dp_in_close = 1; spin_unlock_irqrestore( &priv->dp_port_lock, flags ); /* tell line discipline to process only XON/XOFF */ - tty->closing = 1; + tty->closing = 1; /* wait for output to drain */ if( (filp->f_flags&(O_NDELAY|O_NONBLOCK)) == 0 ) { @@ -1624,6 +1628,7 @@ dbg( "digi_close: TOP: port=%d, open_count=%d", priv->dp_port_num, port->open_co tty->closing = 0; +exit: spin_lock_irqsave( &priv->dp_port_lock, flags ); priv->dp_write_urb_in_use = 0; priv->dp_in_close = 0; -- cgit v1.2.3 From 13319e150b1e6b880cbc72e0efcfccd6d00dbde5 Mon Sep 17 00:00:00 2001 From: David Brownell Date: Fri, 3 Sep 2004 17:29:55 +0200 Subject: [PATCH] USB: OHCI init cleanups This ought to fix the NS9750 init issue, and make the AMD756 case at least somewhat better. It makes the init go "by the book" in more ways, and formalizes one quirk. Various OHCI init/reset cleanups for different silicion environments: - Reset a bit more "by the book". * Define a new quirk flag for the SiS and OPTi problem seen earlier. Since 2.4 we've always worked around that quirk, even though we've not seen that on other chips; but it's "wrong" and doesn't work on some chips (notably NetSilicon NS9750). The quirk still seems to be needed for SiS, but either this test machine is too fast for the OPTi problem to show up, or the frame timing setup problem there came from a now-fixed bug. * Look at the HC state before resetting it; depending on whether it was previously owned by BIOS, SMM, an OS, or nobody, different USB signaling (and timings) might be needed. * Re-init the frame timings right after soft reset, rather than later (potentially too much later). * Restore a reset in the PCI startup code, so this logic more closely resembles the non-PCI paths (future code sharing). It also makes it easier to guarantee a 1-millisecond ceiling between reset and "go". An earlier reset is being done to help workaround BIOS-related problems on some boards, but we may need an even earlier one (as a PCI quirk, before IRQs get reconfigured). - Add an explicit #define to disable the BIOS/SMM handoff; it's not just HPPA, many embedded chips don't expect BIOS either. - There are reports of AMD 756 machines disliking the OHCI suspend patch of a few months back. Erratum #10 partly explains that, so now root hubs won't autosuspend on those Slot-A era chips. - Other minor fixes * We've got lots of non-PCI OHCI now too, so comments shouldn't be assuming all-is-pci! * Hey, it's unsafe to call hc_reset() in IRQ (after unrecoverable error); so just force a soft reset, don't do the whole thing. Tested on half a dozen different OHCI versions, but maybe some other versions of OHCI will be sensitive to one of these changes. Signed-off-by: David Brownell Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-hcd.c | 74 +++++++++++++++++++++++++++++++-------------- drivers/usb/host/ohci-pci.c | 18 +++++++++++ drivers/usb/host/ohci.h | 1 + 3 files changed, 71 insertions(+), 22 deletions(-) diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index a087e72b94ae..12cc2844906d 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -2,7 +2,7 @@ * OHCI HCD (Host Controller Driver) for USB. * * (C) Copyright 1999 Roman Weissgaerber - * (C) Copyright 2000-2002 David Brownell + * (C) Copyright 2000-2004 David Brownell * * [ Initialisation is based on Linus' ] * [ uhci code and gregs ohci fragments ] @@ -122,6 +122,16 @@ #define OHCI_INTR_INIT \ (OHCI_INTR_MIE | OHCI_INTR_UE | OHCI_INTR_RD | OHCI_INTR_WDH) +#ifdef __hppa__ +/* On PA-RISC, PDC can leave IR set incorrectly; ignore it there. */ +#define IR_DISABLE +#endif + +#ifdef CONFIG_ARCH_OMAP +/* OMAP doesn't support IR (no SMM; not needed) */ +#define IR_DISABLE +#endif + /*-------------------------------------------------------------------------*/ static const char hcd_name [] = "ohci_hcd"; @@ -407,10 +417,8 @@ static int hc_reset (struct ohci_hcd *ohci) /* also: power/overcurrent flags in roothub.a */ } - /* SMM owns the HC? not for long! - * On PA-RISC, PDC can leave IR set incorrectly; ignore it there. - */ -#ifndef __hppa__ +#ifndef IR_DISABLE + /* SMM owns the HC? not for long! */ if (ohci_readl (&ohci->regs->control) & OHCI_CTRL_IR) { ohci_dbg (ohci, "USB HC TakeOver from BIOS/SMM\n"); @@ -435,18 +443,40 @@ static int hc_reset (struct ohci_hcd *ohci) /* Disable HC interrupts */ writel (OHCI_INTR_MIE, &ohci->regs->intrdisable); - ohci_dbg (ohci, "reset, control = 0x%x\n", - ohci_readl (&ohci->regs->control)); - - /* Reset USB (needed by some controllers); RemoteWakeupConnected + /* Reset USB nearly "by the book". RemoteWakeupConnected * saved if boot firmware (BIOS/SMM/...) told us it's connected * (for OHCI integrated on mainboard, it normally is) */ ohci->hc_control = ohci_readl (&ohci->regs->control); - ohci->hc_control &= OHCI_CTRL_RWC; /* hcfs 0 = RESET */ - if (ohci->hc_control) + ohci_dbg (ohci, "resetting from state '%s', control = 0x%x\n", + hcfs2string (ohci->hc_control & OHCI_CTRL_HCFS), + ohci->hc_control); + + if (ohci->hc_control & OHCI_CTRL_RWC + && !(ohci->flags & OHCI_QUIRK_AMD756)) ohci->hcd.can_wakeup = 1; + + switch (ohci->hc_control & OHCI_CTRL_HCFS) { + case OHCI_USB_OPER: + temp = 0; + break; + case OHCI_USB_SUSPEND: + case OHCI_USB_RESUME: + ohci->hc_control &= OHCI_CTRL_RWC; + ohci->hc_control |= OHCI_USB_RESUME; + temp = 10 /* msec wait */; + break; + // case OHCI_USB_RESET: + default: + ohci->hc_control &= OHCI_CTRL_RWC; + ohci->hc_control |= OHCI_USB_RESET; + temp = 50 /* msec wait */; + break; + } writel (ohci->hc_control, &ohci->regs->control); + // flush the writes + (void) ohci_readl (&ohci->regs->control); + msleep(temp); if (power_switching) { unsigned ports = roothub_a (ohci) & RH_A_NDP; @@ -455,9 +485,8 @@ static int hc_reset (struct ohci_hcd *ohci) writel (RH_PS_LSDA, &ohci->regs->roothub.portstatus [temp]); } - // flush those pci writes + // flush those writes (void) ohci_readl (&ohci->regs->control); - msleep (50); /* HC Reset requires max 10 us delay */ writel (OHCI_HCR, &ohci->regs->cmdstatus); @@ -469,6 +498,7 @@ static int hc_reset (struct ohci_hcd *ohci) } udelay (1); } + periodic_reinit (ohci); /* now we're in the SUSPEND state ... must go OPERATIONAL * within 2msec else HC enters RESUME @@ -477,10 +507,11 @@ static int hc_reset (struct ohci_hcd *ohci) * (SiS, OPTi ...), so reset again instead. SiS doesn't need * this if we write fmInterval after we're OPERATIONAL. */ - writel (ohci->hc_control, &ohci->regs->control); - // flush those pci writes - (void) ohci_readl (&ohci->regs->control); - + if (ohci->flags & OHCI_QUIRK_INITRESET) { + writel (ohci->hc_control, &ohci->regs->control); + // flush those writes + (void) ohci_readl (&ohci->regs->control); + } return 0; } @@ -506,8 +537,6 @@ static int hc_start (struct ohci_hcd *ohci) /* a reset clears this */ writel ((u32) ohci->hcca_dma, &ohci->regs->hcca); - periodic_reinit (ohci); - /* some OHCI implementations are finicky about how they init. * bogus values here mean not even enumeration could work. */ @@ -553,7 +582,7 @@ static int hc_start (struct ohci_hcd *ohci) writel (tmp, &ohci->regs->roothub.a); writel (RH_HS_LPSC, &ohci->regs->roothub.status); writel (power_switching ? RH_B_PPCM : 0, &ohci->regs->roothub.b); - // flush those pci writes + // flush those writes (void) ohci_readl (&ohci->regs->control); // POTPGT delay is bits 24-31, in 2 ms units. @@ -620,7 +649,8 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd, struct pt_regs *ptregs) // e.g. due to PCI Master/Target Abort ohci_dump (ohci, 1); - hc_reset (ohci); + ohci->hc_control &= OHCI_CTRL_RWC; /* hcfs 0 = RESET */ + writel (ohci->hc_control, &ohci->regs->control); } if (ints & OHCI_INTR_RD) { @@ -655,7 +685,7 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd, struct pt_regs *ptregs) if (HCD_IS_RUNNING(ohci->hcd.state)) { writel (ints, ®s->intrstatus); writel (OHCI_INTR_MIE, ®s->intrenable); - // flush those pci writes + // flush those writes (void) ohci_readl (&ohci->regs->control); } diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index 73d9aee657d5..bf0c5d8ec231 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c @@ -61,6 +61,7 @@ ohci_pci_start (struct usb_hcd *hcd) && pdev->device == 0x740c) { ohci->flags = OHCI_QUIRK_AMD756; ohci_info (ohci, "AMD756 erratum 4 workaround\n"); + // also somewhat erratum 10 (suspend/resume issues) } /* FIXME for some of the early AMD 760 southbridges, OHCI @@ -75,6 +76,8 @@ ohci_pci_start (struct usb_hcd *hcd) && pdev->device == 0xc861) { ohci_info (ohci, "WARNING: OPTi workarounds unavailable\n"); + /* OPTi sometimes acts wierd during init */ + ohci->flags = OHCI_QUIRK_INITRESET; } /* Check for NSC87560. We have to look at the bridge (fn1) to @@ -92,6 +95,12 @@ ohci_pci_start (struct usb_hcd *hcd) ohci_info (ohci, "Using NSC SuperIO setup\n"); } } + + /* SiS sometimes acts wierd during init */ + else if (pdev->vendor == PCI_VENDOR_ID_SI) { + ohci->flags = OHCI_QUIRK_INITRESET; + ohci_info(ohci, "SiS init quirk\n"); + } } @@ -101,6 +110,15 @@ ohci_pci_start (struct usb_hcd *hcd) return ret; } + /* NOTE: this is a second reset. the first one helps + * keep bios/smm irqs from making trouble, but it was + * probably more than 1msec ago... + */ + if (hc_reset (ohci) < 0) { + ohci_stop (hcd); + return -ENODEV; + } + if (hc_start (ohci) < 0) { ohci_err (ohci, "can't start\n"); ohci_stop (hcd); diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h index dcc511662c2c..c60f445d0011 100644 --- a/drivers/usb/host/ohci.h +++ b/drivers/usb/host/ohci.h @@ -387,6 +387,7 @@ struct ohci_hcd { unsigned long flags; /* for HC bugs */ #define OHCI_QUIRK_AMD756 0x01 /* erratum #4 */ #define OHCI_QUIRK_SUPERIO 0x02 /* natsemi */ +#define OHCI_QUIRK_INITRESET 0x04 /* SiS, OPTi, ... */ // there are also chip quirks/bugs in init logic /* -- cgit v1.2.3 From b0c177eccd1e99941bf1eb70b6b5c636c6a9fdb5 Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Fri, 3 Sep 2004 17:30:33 +0200 Subject: [PATCH] USB: Add B&B Electronics VID/PIDs to ftdi_sio This patch adds VID/PIDs for a few FTDI-based USB serial devices from B&B Electronics to the ftdi_sio driver. Signed-off-by: Ian Abbott Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/ftdi_sio.c | 9 +++++++++ drivers/usb/serial/ftdi_sio.h | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index 9073ea84d8d3..b72f7ad4d05e 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -368,6 +368,9 @@ static struct usb_device_id id_table_8U232AM [] = { { USB_DEVICE_VER(INTREPID_VID, INTREPID_NEOVI_PID, 0, 0x3ff) }, { USB_DEVICE_VER(FALCOM_VID, FALCOM_TWIST_PID, 0, 0x3ff) }, { USB_DEVICE_VER(FTDI_VID, FTDI_SUUNTO_SPORTS_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(BANDB_VID, BANDB_USOTL4_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(BANDB_VID, BANDB_USTL4_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(BANDB_VID, BANDB_USO9ML2_PID, 0, 0x3ff) }, { } /* Terminating entry */ }; @@ -478,6 +481,9 @@ static struct usb_device_id id_table_FT232BM [] = { { USB_DEVICE_VER(INTREPID_VID, INTREPID_NEOVI_PID, 0x400, 0xffff) }, { USB_DEVICE_VER(FALCOM_VID, FALCOM_TWIST_PID, 0x400, 0xffff) }, { USB_DEVICE_VER(FTDI_VID, FTDI_SUUNTO_SPORTS_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(BANDB_VID, BANDB_USOTL4_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(BANDB_VID, BANDB_USTL4_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(BANDB_VID, BANDB_USO9ML2_PID, 0x400, 0xffff) }, { } /* Terminating entry */ }; @@ -595,6 +601,9 @@ static struct usb_device_id id_table_combined [] = { { USB_DEVICE(INTREPID_VID, INTREPID_NEOVI_PID) }, { USB_DEVICE(FALCOM_VID, FALCOM_TWIST_PID) }, { USB_DEVICE(FTDI_VID, FTDI_SUUNTO_SPORTS_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_USOTL4_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_USTL4_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_USO9ML2_PID) }, { } /* Terminating entry */ }; diff --git a/drivers/usb/serial/ftdi_sio.h b/drivers/usb/serial/ftdi_sio.h index 232213b02860..bc2b6d5aacaa 100644 --- a/drivers/usb/serial/ftdi_sio.h +++ b/drivers/usb/serial/ftdi_sio.h @@ -225,6 +225,14 @@ */ #define FTDI_SUUNTO_SPORTS_PID 0xF680 /* Suunto Sports instrument */ +/* + * Definitions for B&B Electronics products. + */ +#define BANDB_VID 0x0856 /* B&B Electronics Vendor ID */ +#define BANDB_USOTL4_PID 0xAC01 /* USOTL4 Isolated RS-485 Converter */ +#define BANDB_USTL4_PID 0xAC02 /* USTL4 RS-485 Converter */ +#define BANDB_USO9ML2_PID 0xAC03 /* USO9ML2 Isolated RS-232 Converter */ + /* Commands */ #define FTDI_SIO_RESET 0 /* Reset the port */ #define FTDI_SIO_MODEM_CTRL 1 /* Set the modem control register */ -- cgit v1.2.3 From 2894dd886913fbe990411ac061626b360bbc0262 Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Fri, 3 Sep 2004 17:31:05 +0200 Subject: [PATCH] USB: "Lost sync on frames" error in konicawc module From: http://bugme.osdl.org/show_bug.cgi?id=3286 The kernel keeps printing "Lost sync on frames" error messages as soon as a program tries to access the webcam. No video data can be retrieved from the webcam. The following patch seems enough to solve the problem. (just inverting the order at which the old and new data blocks are sent to the user). Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- drivers/usb/media/konicawc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/media/konicawc.c b/drivers/usb/media/konicawc.c index 3c741a8f5b73..3376654ca051 100644 --- a/drivers/usb/media/konicawc.c +++ b/drivers/usb/media/konicawc.c @@ -362,8 +362,8 @@ static void konicawc_isoc_irq(struct urb *urb, struct pt_regs *regs) else if (!urb->status && !cam->last_data_urb->status) len = konicawc_compress_iso(uvd, cam->last_data_urb, urb); - resubmit_urb(uvd, urb); resubmit_urb(uvd, cam->last_data_urb); + resubmit_urb(uvd, urb); cam->last_data_urb = NULL; uvd->stats.urb_length = len; uvd->stats.data_count += len; -- cgit v1.2.3 From b374dfa1487fc64b03884da73322e4944df0b668 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Fri, 3 Sep 2004 17:31:41 +0200 Subject: [PATCH] USB: Remove inappropriate unusual_devs.h entry A couple of months ago you applied a patch from Torsten Scherer to create a new unusual_devs.h entry. In further discussions with him I learned that the entry wasn't needed to access the device; the only reason for it was as a workaround for some old, buggy hotplug program on his system. Now Evan Fletcher reports that the entry actively prevents him from using his device. For me that's the last straw, so here's a patch to remove the entry. Torsten should be okay if he simply upgrades his hotplug package or removes the buggy program. On Mon, 30 Aug 2004, Evan Fletcher wrote: > Hi list, > > I have a Bytecc 5.25" External Enclosure, model ME-320U2F, that has > both USB 2.0 and Firewire connections > (http://www.byteccusa.com/product/enclosure/ME-320.htm). It used to > work fine on earlier Linux 2.6 kernels, but stopped working a few > revisions ago. > > I tracked the problem down to an entry in unusual_devs.h: > > /* : I don't know the name of the bridge > * manufacturer, but I've got an external USB drive by the Revoltec company > * that needs this. otherwise the drive is recognized as /dev/sda, but any > * access to it blocks indefinitely. > */ > UNUSUAL_DEV( 0x0402, 0x5621, 0x0103, 0x0103, > "Revoltec", > "USB/IDE Bridge (ATA/ATAPI)", > US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_FIX_INQUIRY), <...> > So, is there some way that this entry can be modified so that my DVD+R > works properly, and Mr. Scherer can still use his Revoltec external > disk? > > Thank you, > Evan Fletcher Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/unusual_devs.h | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h index 4fe25f6b0432..69554115a098 100644 --- a/drivers/usb/storage/unusual_devs.h +++ b/drivers/usb/storage/unusual_devs.h @@ -68,16 +68,6 @@ UNUSUAL_DEV( 0x03f0, 0x0307, 0x0001, 0x0001, US_SC_8070, US_PR_SCM_ATAPI, init_8200e, 0), #endif -/* : I don't know the name of the bridge - * manufacturer, but I've got an external USB drive by the Revoltec company - * that needs this. otherwise the drive is recognized as /dev/sda, but any - * access to it blocks indefinitely. - */ -UNUSUAL_DEV( 0x0402, 0x5621, 0x0103, 0x0103, - "Revoltec", - "USB/IDE Bridge (ATA/ATAPI)", - US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_FIX_INQUIRY), - /* Deduced by Jonathan Woithe * Entry needed for flags: US_FL_FIX_INQUIRY because initial inquiry message * always fails and confuses drive. -- cgit v1.2.3 From e3c4c5cd31f4ac69525de5e70bfaea9d86fded96 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Fri, 3 Sep 2004 17:32:12 +0200 Subject: [PATCH] HCD PCI probe: print actual, not ioremapped, address I think the USB HCD should print the actual PCI memory address, not the ioremapped address. AFAIK, there's no reason the ioremapped address has to have any fixed relationship to the actual address. Also, this makes it match what's in /proc/iomem. I also added a leading "0x". Example from ia64: - ehci_hcd 0000:00:01.2: irq 52, pci mem c000000080021000 + ehci_hcd 0000:00:01.2: irq 52, pci mem 0x80021000 USB HCD: print actual PCI mem address, not the ioremapped value. Signed-off-by: Bjorn Helgaas Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hcd-pci.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index e7a7ba51371f..f6f9c3b2325c 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c @@ -188,9 +188,9 @@ clean_3: } hcd->irq = dev->irq; - dev_info (hcd->self.controller, "irq %s, %s %p\n", bufp, + dev_info (hcd->self.controller, "irq %s, %s 0x%lx\n", bufp, (driver->flags & HCD_MEMORY) ? "pci mem" : "io base", - base); + resource); usb_bus_init (&hcd->self); hcd->self.op = &usb_hcd_operations; -- cgit v1.2.3 From cbed817c10334e2baa05f087a4f65e17d0bbfd22 Mon Sep 17 00:00:00 2001 From: Maximilian Attems Date: Fri, 3 Sep 2004 17:32:49 +0200 Subject: [PATCH] usb/tiglusb: insert set_current_state() before schedule_timeout() Insert set_current_state() so schedule_timeout() functions as expected. Signed-off-by: Nishanth Aravamudan Signed-off-by: Maximilian Attems Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/tiglusb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/misc/tiglusb.c b/drivers/usb/misc/tiglusb.c index 0f9c5753772d..f902884a7bbb 100644 --- a/drivers/usb/misc/tiglusb.c +++ b/drivers/usb/misc/tiglusb.c @@ -115,6 +115,7 @@ tiglusb_open (struct inode *inode, struct file *filp) return -EBUSY; } + set_current_state(TASK_INTERRUPTIBLE); schedule_timeout (HZ / 2); if (signal_pending (current)) { -- cgit v1.2.3 From c7c58cf31825c7f89a5f8d5043057c54ba6d4001 Mon Sep 17 00:00:00 2001 From: Maximilian Attems Date: Fri, 3 Sep 2004 17:33:20 +0200 Subject: [PATCH] usb/dabusb: insert set_current_state() before schedule_timeout() After discussing this patch with Mark Hollomon, I think it is much safer / better to leave the conditional check within the while loop. This way the mutex state is as expected and maintainability is not compromised. The previous patch should not be applied. Description: Inserts appropriate set_current_state() call so that schedule_timeout() functions as expected. Signed-off-by: Nishanth Aravamudan Signed-off-by: Maximilian Attems Signed-off-by: Greg Kroah-Hartman --- drivers/usb/media/dabusb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/media/dabusb.c b/drivers/usb/media/dabusb.c index 0e5425f328d0..4cc6e57dfa30 100644 --- a/drivers/usb/media/dabusb.c +++ b/drivers/usb/media/dabusb.c @@ -598,6 +598,7 @@ static int dabusb_open (struct inode *inode, struct file *file) if (file->f_flags & O_NONBLOCK) { return -EBUSY; } + set_current_state(TASK_INTERRUPTIBLE); schedule_timeout (HZ / 2); if (signal_pending (current)) { -- cgit v1.2.3 From 6e14531432bcc3d41a640f6ce77a65a91f03a98f Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Fri, 3 Sep 2004 17:33:55 +0200 Subject: [PATCH] USB: Nag message for usb_kill_urb This patch is only for nuisance value. It puts a nag message in the system log every time usb_unlink_urb() is called for synchronous unlinking. My hope is this will speed the process of converting drivers to use usb_kill_urb(). Don't apply this if it generates too much noise, but otherwise go ahead. A little prodding never hurt anyone. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/urb.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/core/urb.c b/drivers/usb/core/urb.c index 3c14361bbeb3..376e0e939d00 100644 --- a/drivers/usb/core/urb.c +++ b/drivers/usb/core/urb.c @@ -451,6 +451,9 @@ int usb_unlink_urb(struct urb *urb) if (!urb) return -EINVAL; if (!(urb->transfer_flags & URB_ASYNC_UNLINK)) { + printk(KERN_NOTICE "usb_unlink_urb() is deprecated for " + "synchronous unlinks. Use usb_kill_urb()\n"); + WARN_ON(1); usb_kill_urb(urb); return 0; } -- cgit v1.2.3 From cf59b8531ba7787a3e9871c96c699399aa06450e Mon Sep 17 00:00:00 2001 From: Al Borchers Date: Fri, 3 Sep 2004 18:29:06 +0200 Subject: [PATCH] USB: circular buffer for pl2303 Here is the patch adding a circular buffer to pl2303 updated to 2.6.9-rc1. Phil and I both tested this. (Phil tested a slightly different earlier version.) This fixes the carriage return newline problem Olaf Hering reported and helps Phil with hotsyncing his phone. This patch also fixes a problem that would sometimes leave a pl2303 port unable to send data, reporting "already writing", after closing the port while writing was in progress. This happened about 1/3 of the time in my tests with the stock 2.6.9-rc1 pl2303 driver. - Added a circular write buffer, protected by the existing spin lock. - Write_room and chars_in_buffer now report room and chars in the circular buffer. - Added a "bounce buffer" when transfering data from user space to the circular buffer--needed for locking. - Replaced (urb->status != -EINPROGRESS) with a private write_urb_in_use flag protected by the existing spin lock. Clear this flag when the urb is unlinked. - Free memory on failed startup. - These changes make ONLCR mapping work and fix a bug that would sometimes leave the port unable to write, reporting "already writing", after closing the port while writing was in progress. Signed-off-by: Al Borchers Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/pl2303.c | 349 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 324 insertions(+), 25 deletions(-) diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c index c61e56829a96..a9dc4ff4089b 100644 --- a/drivers/usb/serial/pl2303.c +++ b/drivers/usb/serial/pl2303.c @@ -55,11 +55,24 @@ /* * Version Information */ -#define DRIVER_VERSION "v0.11" +#define DRIVER_VERSION "v0.12" #define DRIVER_DESC "Prolific PL2303 USB to serial adaptor driver" static int debug; +#define PL2303_BUF_SIZE 1024 +#define PL2303_TMP_BUF_SIZE 1024 + +static char pl2303_tmp_buf[PL2303_TMP_BUF_SIZE]; +static DECLARE_MUTEX(pl2303_tmp_buf_sem); + +struct pl2303_buf { + unsigned int buf_size; + char *buf_buf; + char *buf_get; + char *buf_put; +}; + static struct usb_device_id id_table [] = { { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID) }, { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_RSAQ2) }, @@ -134,12 +147,23 @@ static void pl2303_read_bulk_callback (struct urb *urb, struct pt_regs *regs); static void pl2303_write_bulk_callback (struct urb *urb, struct pt_regs *regs); static int pl2303_write (struct usb_serial_port *port, int from_user, const unsigned char *buf, int count); +static void pl2303_send (struct usb_serial_port *port); +static int pl2303_write_room(struct usb_serial_port *port); +static int pl2303_chars_in_buffer(struct usb_serial_port *port); static void pl2303_break_ctl(struct usb_serial_port *port,int break_state); static int pl2303_tiocmget (struct usb_serial_port *port, struct file *file); static int pl2303_tiocmset (struct usb_serial_port *port, struct file *file, unsigned int set, unsigned int clear); static int pl2303_startup (struct usb_serial *serial); static void pl2303_shutdown (struct usb_serial *serial); +static struct pl2303_buf *pl2303_buf_alloc(unsigned int size); +static void pl2303_buf_free(struct pl2303_buf *pb); +static unsigned int pl2303_buf_data_avail(struct pl2303_buf *pb); +static unsigned int pl2303_buf_space_avail(struct pl2303_buf *pb); +static unsigned int pl2303_buf_put(struct pl2303_buf *pb, const char *buf, + unsigned int count); +static unsigned int pl2303_buf_get(struct pl2303_buf *pb, char *buf, + unsigned int count); /* All of the device info needed for the PL2303 SIO serial converter */ @@ -162,6 +186,8 @@ static struct usb_serial_device_type pl2303_device = { .read_bulk_callback = pl2303_read_bulk_callback, .read_int_callback = pl2303_read_int_callback, .write_bulk_callback = pl2303_write_bulk_callback, + .write_room = pl2303_write_room, + .chars_in_buffer = pl2303_chars_in_buffer, .attach = pl2303_startup, .shutdown = pl2303_shutdown, }; @@ -174,6 +200,8 @@ enum pl2303_type { struct pl2303_private { spinlock_t lock; + struct pl2303_buf *buf; + int write_urb_in_use; wait_queue_head_t delta_msr_wait; u8 line_control; u8 line_status; @@ -201,14 +229,28 @@ static int pl2303_startup (struct usb_serial *serial) for (i = 0; i < serial->num_ports; ++i) { priv = kmalloc (sizeof (struct pl2303_private), GFP_KERNEL); if (!priv) - return -ENOMEM; + goto cleanup; memset (priv, 0x00, sizeof (struct pl2303_private)); spin_lock_init(&priv->lock); + priv->buf = pl2303_buf_alloc(PL2303_BUF_SIZE); + if (priv->buf == NULL) { + kfree(priv); + goto cleanup; + } init_waitqueue_head(&priv->delta_msr_wait); priv->type = type; usb_set_serial_port_data(serial->port[i], priv); } return 0; + +cleanup: + for (--i; i>=0; --i) { + priv = usb_get_serial_port_data(serial->port[i]); + pl2303_buf_free(priv->buf); + kfree(priv); + usb_set_serial_port_data(serial->port[i], NULL); + } + return -ENOMEM; } static int set_control_lines (struct usb_device *dev, u8 value) @@ -224,40 +266,109 @@ static int set_control_lines (struct usb_device *dev, u8 value) static int pl2303_write (struct usb_serial_port *port, int from_user, const unsigned char *buf, int count) { - int result; + struct pl2303_private *priv = usb_get_serial_port_data(port); + unsigned long flags; dbg("%s - port %d, %d bytes", __FUNCTION__, port->number, count); if (!count) return count; - if (port->write_urb->status == -EINPROGRESS) { - dbg("%s - already writing", __FUNCTION__); - return 0; - } - - count = (count > port->bulk_out_size) ? port->bulk_out_size : count; if (from_user) { - if (copy_from_user (port->write_urb->transfer_buffer, buf, count)) + if (count > PL2303_TMP_BUF_SIZE) + count = PL2303_TMP_BUF_SIZE; + down(&pl2303_tmp_buf_sem); + if (copy_from_user(pl2303_tmp_buf, buf, count)) { + up(&pl2303_tmp_buf_sem); return -EFAULT; - } else { - memcpy (port->write_urb->transfer_buffer, buf, count); + } + buf = pl2303_tmp_buf; } - + + spin_lock_irqsave(&priv->lock, flags); + count = pl2303_buf_put(priv->buf, buf, count); + spin_unlock_irqrestore(&priv->lock, flags); + + if (from_user) + up(&pl2303_tmp_buf_sem); + + pl2303_send(port); + + return count; +} + +static void pl2303_send(struct usb_serial_port *port) +{ + int count, result; + struct pl2303_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + + dbg("%s - port %d", __FUNCTION__, port->number); + + spin_lock_irqsave(&priv->lock, flags); + + if (priv->write_urb_in_use) { + spin_unlock_irqrestore(&priv->lock, flags); + return; + } + + count = pl2303_buf_get(priv->buf, port->write_urb->transfer_buffer, + port->bulk_out_size); + + if (count == 0) { + spin_unlock_irqrestore(&priv->lock, flags); + return; + } + + priv->write_urb_in_use = 1; + + spin_unlock_irqrestore(&priv->lock, flags); + usb_serial_debug_data(debug, &port->dev, __FUNCTION__, count, port->write_urb->transfer_buffer); port->write_urb->transfer_buffer_length = count; port->write_urb->dev = port->serial->dev; result = usb_submit_urb (port->write_urb, GFP_ATOMIC); - if (result) + if (result) { dev_err(&port->dev, "%s - failed submitting write urb, error %d\n", __FUNCTION__, result); - else - result = count; + priv->write_urb_in_use = 0; + // TODO: reschedule pl2303_send + } - return result; + schedule_work(&port->work); } +static int pl2303_write_room(struct usb_serial_port *port) +{ + struct pl2303_private *priv = usb_get_serial_port_data(port); + int room = 0; + unsigned long flags; + + dbg("%s - port %d", __FUNCTION__, port->number); + spin_lock_irqsave(&priv->lock, flags); + room = pl2303_buf_space_avail(priv->buf); + spin_unlock_irqrestore(&priv->lock, flags); + + dbg("%s - returns %d", __FUNCTION__, room); + return room; +} + +static int pl2303_chars_in_buffer(struct usb_serial_port *port) +{ + struct pl2303_private *priv = usb_get_serial_port_data(port); + int chars = 0; + unsigned long flags; + + dbg("%s - port %d", __FUNCTION__, port->number); + + spin_lock_irqsave(&priv->lock, flags); + chars = pl2303_buf_data_avail(priv->buf); + spin_unlock_irqrestore(&priv->lock, flags); + + dbg("%s - returns %d", __FUNCTION__, chars); + return chars; +} static void pl2303_set_termios (struct usb_serial_port *port, struct termios *old_termios) { @@ -422,7 +533,7 @@ static void pl2303_set_termios (struct usb_serial_port *port, struct termios *ol } kfree (buf); -} +} static int pl2303_open (struct usb_serial_port *port, struct file *filp) { @@ -461,7 +572,7 @@ static int pl2303_open (struct usb_serial_port *port, struct file *filp) FISH (VENDOR_READ_REQUEST_TYPE, VENDOR_READ_REQUEST, 0x8383, 0); SOUP (VENDOR_WRITE_REQUEST_TYPE, VENDOR_WRITE_REQUEST, 0, 1); SOUP (VENDOR_WRITE_REQUEST_TYPE, VENDOR_WRITE_REQUEST, 1, 0); - + if (priv->type == HX) { /* HX chip */ SOUP (VENDOR_WRITE_REQUEST_TYPE, VENDOR_WRITE_REQUEST, 2, 0x44); @@ -672,12 +783,17 @@ static void pl2303_break_ctl (struct usb_serial_port *port, int break_state) static void pl2303_shutdown (struct usb_serial *serial) { int i; + struct pl2303_private *priv; dbg("%s", __FUNCTION__); for (i = 0; i < serial->num_ports; ++i) { - kfree (usb_get_serial_port_data(serial->port[i])); - usb_set_serial_port_data(serial->port[i], NULL); + priv = usb_get_serial_port_data(serial->port[i]); + if (priv) { + pl2303_buf_free(priv->buf); + kfree(priv); + usb_set_serial_port_data(serial->port[i], NULL); + } } } @@ -815,11 +931,23 @@ static void pl2303_read_bulk_callback (struct urb *urb, struct pt_regs *regs) static void pl2303_write_bulk_callback (struct urb *urb, struct pt_regs *regs) { struct usb_serial_port *port = (struct usb_serial_port *) urb->context; + struct pl2303_private *priv = usb_get_serial_port_data(port); int result; dbg("%s - port %d", __FUNCTION__, port->number); - - if (urb->status) { + + switch (urb->status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dbg("%s - urb shutting down with status: %d", __FUNCTION__, urb->status); + priv->write_urb_in_use = 0; + return; + default: /* error in the urb, so we have to resubmit it */ dbg("%s - Overflow in write", __FUNCTION__); dbg("%s - nonzero write bulk status received: %d", __FUNCTION__, urb->status); @@ -828,14 +956,185 @@ static void pl2303_write_bulk_callback (struct urb *urb, struct pt_regs *regs) result = usb_submit_urb (port->write_urb, GFP_ATOMIC); if (result) dev_err(&urb->dev->dev, "%s - failed resubmitting write urb, error %d\n", __FUNCTION__, result); + else + return; + } - return; + priv->write_urb_in_use = 0; + + /* send any buffered data */ + pl2303_send(port); +} + + +/* + * pl2303_buf_alloc + * + * Allocate a circular buffer and all associated memory. + */ + +static struct pl2303_buf *pl2303_buf_alloc(unsigned int size) +{ + + struct pl2303_buf *pb; + + + if (size == 0) + return NULL; + + pb = (struct pl2303_buf *)kmalloc(sizeof(struct pl2303_buf), GFP_KERNEL); + if (pb == NULL) + return NULL; + + pb->buf_buf = kmalloc(size, GFP_KERNEL); + if (pb->buf_buf == NULL) { + kfree(pb); + return NULL; } - schedule_work(&port->work); + pb->buf_size = size; + pb->buf_get = pb->buf_put = pb->buf_buf; + + return pb; + +} + + +/* + * pl2303_buf_free + * + * Free the buffer and all associated memory. + */ + +void pl2303_buf_free(struct pl2303_buf *pb) +{ + if (pb != NULL) { + if (pb->buf_buf != NULL) + kfree(pb->buf_buf); + kfree(pb); + } } +/* + * pl2303_buf_data_avail + * + * Return the number of bytes of data available in the circular + * buffer. + */ + +static unsigned int pl2303_buf_data_avail(struct pl2303_buf *pb) +{ + if (pb != NULL) + return ((pb->buf_size + pb->buf_put - pb->buf_get) % pb->buf_size); + else + return 0; +} + + +/* + * pl2303_buf_space_avail + * + * Return the number of bytes of space available in the circular + * buffer. + */ + +static unsigned int pl2303_buf_space_avail(struct pl2303_buf *pb) +{ + if (pb != NULL) + return ((pb->buf_size + pb->buf_get - pb->buf_put - 1) % pb->buf_size); + else + return 0; +} + + +/* + * pl2303_buf_put + * + * Copy data data from a user buffer and put it into the circular buffer. + * Restrict to the amount of space available. + * + * Return the number of bytes copied. + */ + +static unsigned int pl2303_buf_put(struct pl2303_buf *pb, const char *buf, + unsigned int count) +{ + + unsigned int len; + + + if (pb == NULL) + return 0; + + len = pl2303_buf_space_avail(pb); + if (count > len) + count = len; + + if (count == 0) + return 0; + + len = pb->buf_buf + pb->buf_size - pb->buf_put; + if (count > len) { + memcpy(pb->buf_put, buf, len); + memcpy(pb->buf_buf, buf+len, count - len); + pb->buf_put = pb->buf_buf + count - len; + } else { + memcpy(pb->buf_put, buf, count); + if (count < len) + pb->buf_put += count; + else /* count == len */ + pb->buf_put = pb->buf_buf; + } + + return count; + +} + + +/* + * pl2303_buf_get + * + * Get data from the circular buffer and copy to the given buffer. + * Restrict to the amount of data available. + * + * Return the number of bytes copied. + */ + +static unsigned int pl2303_buf_get(struct pl2303_buf *pb, char *buf, + unsigned int count) +{ + + unsigned int len; + + + if (pb == NULL) + return 0; + + len = pl2303_buf_data_avail(pb); + if (count > len) + count = len; + + if (count == 0) + return 0; + + len = pb->buf_buf + pb->buf_size - pb->buf_get; + if (count > len) { + memcpy(buf, pb->buf_get, len); + memcpy(buf+len, pb->buf_buf, count - len); + pb->buf_get = pb->buf_buf + count - len; + } else { + memcpy(buf, pb->buf_get, count); + if (count < len) + pb->buf_get += count; + else /* count == len */ + pb->buf_get = pb->buf_buf; + } + + return count; + +} + static int __init pl2303_init (void) { int retval; -- cgit v1.2.3 From 66d2dc06414f5e975f4a27213806d4e71091c827 Mon Sep 17 00:00:00 2001 From: Al Borchers Date: Fri, 3 Sep 2004 18:29:45 +0200 Subject: [PATCH] USB: close waits for drain in pl2303 Here is an additional patch for pl2303 in 2.6.9-rc1, to be applied after my previous circular buffer patch. This patch waits for the buffer to drain on close and then clears the buffer. In addition to the obvious features, this also fixes a bug where closing a port with a full buffer would accidentally disable writes when the port was re-opened. The original pl2303 could lose data that was still going out the port on close, and even this patch only solves the problem completely for data rates of 1200 bps and above. Waiting for data to drain from the circular buffer is easy, but the problem is waiting for data to drain from the 256 byte hardware buffer on the device. I don't know how much data is in the hardware buffer, so I just wait long enough for a potentially full hardware buffer to drain, but I don't want to wait that long for low data rates if the buffer isn't full. There is probably some way to query the pl2303 to find out how much data is in its hardware buffer, maybe snooping would reveal that. - Added a wait for data to drain from the driver write buffer when closing. - Added a wait for the data to drain from the device hardware buffer when closing. - Cleared the driver write buffer when closing. Signed-off-by: Al Borchers Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/pl2303.c | 62 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c index a9dc4ff4089b..416ea72951f6 100644 --- a/drivers/usb/serial/pl2303.c +++ b/drivers/usb/serial/pl2303.c @@ -60,6 +60,8 @@ static int debug; +#define PL2303_CLOSING_WAIT (30*HZ) + #define PL2303_BUF_SIZE 1024 #define PL2303_TMP_BUF_SIZE 1024 @@ -158,6 +160,7 @@ static int pl2303_startup (struct usb_serial *serial); static void pl2303_shutdown (struct usb_serial *serial); static struct pl2303_buf *pl2303_buf_alloc(unsigned int size); static void pl2303_buf_free(struct pl2303_buf *pb); +static void pl2303_buf_clear(struct pl2303_buf *pb); static unsigned int pl2303_buf_data_avail(struct pl2303_buf *pb); static unsigned int pl2303_buf_space_avail(struct pl2303_buf *pb); static unsigned int pl2303_buf_put(struct pl2303_buf *pb, const char *buf, @@ -615,13 +618,52 @@ static int pl2303_open (struct usb_serial_port *port, struct file *filp) static void pl2303_close (struct usb_serial_port *port, struct file *filp) { - struct pl2303_private *priv; + struct pl2303_private *priv = usb_get_serial_port_data(port); unsigned long flags; unsigned int c_cflag; int result; + int bps; + long timeout; + wait_queue_t wait; \ dbg("%s - port %d", __FUNCTION__, port->number); + /* wait for data to drain from the buffer */ + spin_lock_irqsave(&priv->lock, flags); + timeout = PL2303_CLOSING_WAIT; + init_waitqueue_entry(&wait, current); + add_wait_queue(&port->tty->write_wait, &wait); + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + if (pl2303_buf_data_avail(priv->buf) == 0 + || timeout == 0 || signal_pending(current) + || !usb_get_intfdata(port->serial->interface)) /* disconnect */ + break; + spin_unlock_irqrestore(&priv->lock, flags); + timeout = schedule_timeout(timeout); + spin_lock_irqsave(&priv->lock, flags); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&port->tty->write_wait, &wait); + /* clear out any remaining data in the buffer */ + pl2303_buf_clear(priv->buf); + spin_unlock_irqrestore(&priv->lock, flags); + + /* wait for characters to drain from the device */ + /* (this is long enough for the entire 256 byte */ + /* pl2303 hardware buffer to drain with no flow */ + /* control for data rates of 1200 bps or more, */ + /* for lower rates we should really know how much */ + /* data is in the buffer to compute a delay */ + /* that is not unnecessarily long) */ + bps = tty_get_baud_rate(port->tty); + if (bps > 1200) + timeout = max((HZ*2560)/bps,HZ/10); + else + timeout = 2*HZ; + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(timeout); + /* shutdown our urbs */ dbg("%s - shutting down urbs", __FUNCTION__); result = usb_unlink_urb (port->write_urb); @@ -646,14 +688,12 @@ static void pl2303_close (struct usb_serial_port *port, struct file *filp) c_cflag = port->tty->termios->c_cflag; if (c_cflag & HUPCL) { /* drop DTR and RTS */ - priv = usb_get_serial_port_data(port); spin_lock_irqsave(&priv->lock, flags); priv->line_control = 0; spin_unlock_irqrestore (&priv->lock, flags); set_control_lines (port->serial->dev, 0); } } - } static int pl2303_tiocmset (struct usb_serial_port *port, struct file *file, @@ -1006,7 +1046,7 @@ static struct pl2303_buf *pl2303_buf_alloc(unsigned int size) * Free the buffer and all associated memory. */ -void pl2303_buf_free(struct pl2303_buf *pb) +static void pl2303_buf_free(struct pl2303_buf *pb) { if (pb != NULL) { if (pb->buf_buf != NULL) @@ -1016,6 +1056,20 @@ void pl2303_buf_free(struct pl2303_buf *pb) } +/* + * pl2303_buf_clear + * + * Clear out all data in the circular buffer. + */ + +static void pl2303_buf_clear(struct pl2303_buf *pb) +{ + if (pb != NULL) + pb->buf_get = pb->buf_put; + /* equivalent to a get of all data available */ +} + + /* * pl2303_buf_data_avail * -- cgit v1.2.3 From ee124eaab952e381d81ab0f4c63f31a4d7bc8bd3 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Sun, 5 Sep 2004 08:53:18 +0200 Subject: USB: remove usbdevfs filesystem name, usbfs is the proper one to use. This has been publicised for years now, and the usvfs name will work just fine with a 2.4 kernel, so we are not breaking backwards compatibility. Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/devices.c | 4 +- drivers/usb/core/devio.c | 10 ++-- drivers/usb/core/inode.c | 121 +++++-------------------------------------- drivers/usb/core/usb.h | 6 +++ include/linux/usb.h | 2 - include/linux/usbdevice_fs.h | 10 ---- 6 files changed, 26 insertions(+), 127 deletions(-) diff --git a/drivers/usb/core/devices.c b/drivers/usb/core/devices.c index 8e535789fce7..15d773fc539d 100644 --- a/drivers/usb/core/devices.c +++ b/drivers/usb/core/devices.c @@ -149,7 +149,7 @@ static const struct class_info clas_info[] = /*****************************************************************/ -void usbdevfs_conn_disc_event(void) +void usbfs_conn_disc_event(void) { conndiscevcnt++; wake_up(&deviceconndiscwq); @@ -682,7 +682,7 @@ static loff_t usb_device_lseek(struct file * file, loff_t offset, int orig) return ret; } -struct file_operations usbdevfs_devices_fops = { +struct file_operations usbfs_devices_fops = { .llseek = usb_device_lseek, .read = usb_device_read, .poll = usb_device_poll, diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index 17ca7690dbde..0b3bf6634ee4 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -21,7 +21,7 @@ * * $Id: devio.c,v 1.7 2000/02/01 17:28:48 fliegl Exp $ * - * This file implements the usbdevfs/x/y files, where + * This file implements the usbfs/x/y files, where * x is the bus number and y the device number. * * It allows user space programs/"drivers" to communicate directly @@ -354,7 +354,7 @@ static void driver_disconnect(struct usb_interface *intf) destroy_async_on_interface(ps, ifnum); } -struct usb_driver usbdevfs_driver = { +struct usb_driver usbfs_driver = { .owner = THIS_MODULE, .name = "usbfs", .probe = driver_probe, @@ -379,7 +379,7 @@ static int claimintf(struct dev_state *ps, unsigned int ifnum) if (!intf) err = -ENOENT; else - err = usb_driver_claim_interface(&usbdevfs_driver, intf, ps); + err = usb_driver_claim_interface(&usbfs_driver, intf, ps); up_write(&usb_bus_type.subsys.rwsem); if (err == 0) set_bit(ifnum, &ps->ifclaimed); @@ -402,7 +402,7 @@ static int releaseintf(struct dev_state *ps, unsigned int ifnum) if (!intf) err = -ENOENT; else if (test_and_clear_bit(ifnum, &ps->ifclaimed)) { - usb_driver_release_interface(&usbdevfs_driver, intf); + usb_driver_release_interface(&usbfs_driver, intf); err = 0; } up_write(&usb_bus_type.subsys.rwsem); @@ -1315,7 +1315,7 @@ static unsigned int usbdev_poll(struct file *file, struct poll_table_struct *wai return mask; } -struct file_operations usbdevfs_device_file_operations = { +struct file_operations usbfs_device_file_operations = { .llseek = usbdev_lseek, .read = usbdev_read, .poll = usbdev_poll, diff --git a/drivers/usb/core/inode.c b/drivers/usb/core/inode.c index c9f57e334a4f..24452d66d576 100644 --- a/drivers/usb/core/inode.c +++ b/drivers/usb/core/inode.c @@ -4,7 +4,7 @@ * inode.c -- Inode/Dentry functions for the USB device file system. * * Copyright (C) 2000 Thomas Sailer (sailer@ife.ee.ethz.ch) - * Copyright (C) 2001,2002 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001,2002,2004 Greg Kroah-Hartman (greg@kroah.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,17 +40,15 @@ #include #include #include +#include "usb.h" static struct super_operations usbfs_ops; static struct file_operations default_file_operations; static struct inode_operations usbfs_dir_inode_operations; -static struct vfsmount *usbdevfs_mount; static struct vfsmount *usbfs_mount; -static int usbdevfs_mount_count; /* = 0 */ static int usbfs_mount_count; /* = 0 */ static int ignore_mount = 0; -static struct dentry *devices_usbdevfs_dentry; static struct dentry *devices_usbfs_dentry; static int num_buses; /* = 0 */ @@ -240,9 +238,6 @@ static int remount(struct super_block *sb, int *flags, char *data) if (usbfs_mount && usbfs_mount->mnt_sb) update_sb(usbfs_mount->mnt_sb); - if (usbdevfs_mount && usbdevfs_mount->mnt_sb) - update_sb(usbdevfs_mount->mnt_sb); - return 0; } @@ -561,28 +556,12 @@ static void fs_remove_file (struct dentry *dentry) /* --------------------------------------------------------------------- */ - - -/* - * The usbdevfs name is now deprecated (as of 2.5.1). - * It will be removed when the 2.7.x development cycle is started. - * You have been warned :) - */ -static struct file_system_type usbdevice_fs_type; - static struct super_block *usb_get_sb(struct file_system_type *fs_type, int flags, const char *dev_name, void *data) { return get_sb_single(fs_type, flags, data, usbfs_fill_super); } -static struct file_system_type usbdevice_fs_type = { - .owner = THIS_MODULE, - .name = "usbdevfs", - .get_sb = usb_get_sb, - .kill_sb = kill_litter_super, -}; - static struct file_system_type usb_fs_type = { .owner = THIS_MODULE, .name = "usbfs", @@ -603,16 +582,10 @@ static int create_special_files (void) ignore_mount = 1; /* create the devices special file */ - retval = simple_pin_fs("usbdevfs", &usbdevfs_mount, &usbdevfs_mount_count); - if (retval) { - err ("Unable to get usbdevfs mount"); - goto exit; - } - retval = simple_pin_fs("usbfs", &usbfs_mount, &usbfs_mount_count); if (retval) { err ("Unable to get usbfs mount"); - goto error_clean_usbdevfs_mount; + goto exit; } ignore_mount = 0; @@ -620,7 +593,7 @@ static int create_special_files (void) parent = usbfs_mount->mnt_sb->s_root; devices_usbfs_dentry = fs_create_file ("devices", listmode | S_IFREG, parent, - NULL, &usbdevfs_devices_fops, + NULL, &usbfs_devices_fops, listuid, listgid); if (devices_usbfs_dentry == NULL) { err ("Unable to create devices usbfs file"); @@ -628,42 +601,19 @@ static int create_special_files (void) goto error_clean_mounts; } - parent = usbdevfs_mount->mnt_sb->s_root; - devices_usbdevfs_dentry = fs_create_file ("devices", - listmode | S_IFREG, parent, - NULL, &usbdevfs_devices_fops, - listuid, listgid); - if (devices_usbdevfs_dentry == NULL) { - err ("Unable to create devices usbfs file"); - retval = -ENODEV; - goto error_remove_file; - } - goto exit; -error_remove_file: - fs_remove_file (devices_usbfs_dentry); - devices_usbfs_dentry = NULL; - error_clean_mounts: simple_release_fs(&usbfs_mount, &usbfs_mount_count); - -error_clean_usbdevfs_mount: - simple_release_fs(&usbdevfs_mount, &usbdevfs_mount_count); - exit: return retval; } static void remove_special_files (void) { - if (devices_usbdevfs_dentry) - fs_remove_file (devices_usbdevfs_dentry); if (devices_usbfs_dentry) fs_remove_file (devices_usbfs_dentry); - devices_usbdevfs_dentry = NULL; devices_usbfs_dentry = NULL; - simple_release_fs(&usbdevfs_mount, &usbdevfs_mount_count); simple_release_fs(&usbfs_mount, &usbfs_mount_count); } @@ -671,11 +621,6 @@ void usbfs_update_special (void) { struct inode *inode; - if (devices_usbdevfs_dentry) { - inode = devices_usbdevfs_dentry->d_inode; - if (inode) - inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; - } if (devices_usbfs_dentry) { inode = devices_usbfs_dentry->d_inode; if (inode) @@ -707,29 +652,16 @@ void usbfs_add_bus(struct usb_bus *bus) return; } - parent = usbdevfs_mount->mnt_sb->s_root; - bus->usbdevfs_dentry = fs_create_file (name, busmode | S_IFDIR, parent, - bus, NULL, busuid, busgid); - if (bus->usbdevfs_dentry == NULL) { - err ("error creating usbdevfs bus entry"); - return; - } - usbfs_update_special(); - usbdevfs_conn_disc_event(); + usbfs_conn_disc_event(); } - void usbfs_remove_bus(struct usb_bus *bus) { if (bus->usbfs_dentry) { fs_remove_file (bus->usbfs_dentry); bus->usbfs_dentry = NULL; } - if (bus->usbdevfs_dentry) { - fs_remove_file (bus->usbdevfs_dentry); - bus->usbdevfs_dentry = NULL; - } --num_buses; if (num_buses <= 0) { @@ -738,7 +670,7 @@ void usbfs_remove_bus(struct usb_bus *bus) } usbfs_update_special(); - usbdevfs_conn_disc_event(); + usbfs_conn_disc_event(); } void usbfs_add_device(struct usb_device *dev) @@ -750,20 +682,12 @@ void usbfs_add_device(struct usb_device *dev) sprintf (name, "%03d", dev->devnum); dev->usbfs_dentry = fs_create_file (name, devmode | S_IFREG, dev->bus->usbfs_dentry, dev, - &usbdevfs_device_file_operations, + &usbfs_device_file_operations, devuid, devgid); if (dev->usbfs_dentry == NULL) { err ("error creating usbfs device entry"); return; } - dev->usbdevfs_dentry = fs_create_file (name, devmode | S_IFREG, - dev->bus->usbdevfs_dentry, dev, - &usbdevfs_device_file_operations, - devuid, devgid); - if (dev->usbdevfs_dentry == NULL) { - err ("error creating usbdevfs device entry"); - return; - } /* Set the size of the device's file to be * equal to the size of the device descriptors. */ @@ -775,11 +699,9 @@ void usbfs_add_device(struct usb_device *dev) } if (dev->usbfs_dentry->d_inode) dev->usbfs_dentry->d_inode->i_size = i_size; - if (dev->usbdevfs_dentry->d_inode) - dev->usbdevfs_dentry->d_inode->i_size = i_size; usbfs_update_special(); - usbdevfs_conn_disc_event(); + usbfs_conn_disc_event(); } void usbfs_remove_device(struct usb_device *dev) @@ -791,10 +713,6 @@ void usbfs_remove_device(struct usb_device *dev) fs_remove_file (dev->usbfs_dentry); dev->usbfs_dentry = NULL; } - if (dev->usbdevfs_dentry) { - fs_remove_file (dev->usbdevfs_dentry); - dev->usbdevfs_dentry = NULL; - } while (!list_empty(&dev->filelist)) { ds = list_entry(dev->filelist.next, struct dev_state, list); list_del_init(&ds->list); @@ -807,51 +725,38 @@ void usbfs_remove_device(struct usb_device *dev) } } usbfs_update_special(); - usbdevfs_conn_disc_event(); + usbfs_conn_disc_event(); } /* --------------------------------------------------------------------- */ -#ifdef CONFIG_PROC_FS static struct proc_dir_entry *usbdir = NULL; -#endif int __init usbfs_init(void) { int retval; - retval = usb_register(&usbdevfs_driver); + retval = usb_register(&usbfs_driver); if (retval) return retval; retval = register_filesystem(&usb_fs_type); if (retval) { - usb_deregister(&usbdevfs_driver); - return retval; - } - retval = register_filesystem(&usbdevice_fs_type); - if (retval) { - unregister_filesystem(&usb_fs_type); - usb_deregister(&usbdevfs_driver); + usb_deregister(&usbfs_driver); return retval; } -#ifdef CONFIG_PROC_FS - /* create mount point for usbdevfs */ + /* create mount point for usbfs */ usbdir = proc_mkdir("usb", proc_bus); -#endif return 0; } void usbfs_cleanup(void) { - usb_deregister(&usbdevfs_driver); + usb_deregister(&usbfs_driver); unregister_filesystem(&usb_fs_type); - unregister_filesystem(&usbdevice_fs_type); -#ifdef CONFIG_PROC_FS if (usbdir) remove_proc_entry("usb", proc_bus); -#endif } diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index f1dff4f4d5d6..b44bb8d7cdfa 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -27,3 +27,9 @@ extern void usb_set_device_state(struct usb_device *udev, /* for labeling diagnostics */ extern const char *usbcore_name; + +/* usbfs stuff */ +extern struct usb_driver usbfs_driver; +extern struct file_operations usbfs_devices_fops; +extern struct file_operations usbfs_device_file_operations; +extern void usbfs_conn_disc_event(void); diff --git a/include/linux/usb.h b/include/linux/usb.h index a43c95a016d7..d07d5aba9e7f 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -264,7 +264,6 @@ struct usb_bus { int bandwidth_isoc_reqs; /* number of Isoc. requests */ struct dentry *usbfs_dentry; /* usbfs dentry entry for the bus */ - struct dentry *usbdevfs_dentry; /* usbdevfs dentry entry for the bus */ struct class_device class_dev; /* class device for this bus */ void (*release)(struct usb_bus *bus); /* function to destroy this bus's memory */ @@ -315,7 +314,6 @@ struct usb_device { struct list_head filelist; struct dentry *usbfs_dentry; /* usbfs dentry entry for the device */ - struct dentry *usbdevfs_dentry; /* usbdevfs dentry entry for the device */ /* * Child devices - these can be either new devices diff --git a/include/linux/usbdevice_fs.h b/include/linux/usbdevice_fs.h index 78f47434b757..af49afaf7bb4 100644 --- a/include/linux/usbdevice_fs.h +++ b/include/linux/usbdevice_fs.h @@ -166,16 +166,6 @@ struct dev_state { unsigned long ifclaimed; }; -/* internal methods & data */ -extern struct usb_driver usbdevfs_driver; -extern struct file_operations usbdevfs_drivers_fops; -extern struct file_operations usbdevfs_devices_fops; -extern struct file_operations usbdevfs_device_file_operations; -extern struct inode_operations usbdevfs_device_inode_operations; -extern struct inode_operations usbdevfs_bus_inode_operations; -extern struct file_operations usbdevfs_bus_file_operations; -extern void usbdevfs_conn_disc_event(void); - #endif /* __KERNEL__ */ /* --------------------------------------------------------------------- */ -- cgit v1.2.3 From 533c4dbbef975b507905b961fc5687b38f312000 Mon Sep 17 00:00:00 2001 From: Maximilian Attems Date: Sun, 5 Sep 2004 09:04:18 +0200 Subject: [PATCH] list_for_each_entry: drivers-usb-core-devices.c Make code more readable with list_for_each_entry. Compile tested. Patch incremental on the list_for_each() change. Signed-off-by: Domen Puncer Signed-off-by: Maximilian Attems Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/devices.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/usb/core/devices.c b/drivers/usb/core/devices.c index 15d773fc539d..195e365675af 100644 --- a/drivers/usb/core/devices.c +++ b/drivers/usb/core/devices.c @@ -569,7 +569,6 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, loff_t *ski static ssize_t usb_device_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) { - struct list_head *buslist; struct usb_bus *bus; ssize_t ret, total_written = 0; loff_t skip_bytes = *ppos; @@ -581,12 +580,9 @@ static ssize_t usb_device_read(struct file *file, char __user *buf, size_t nbyte if (!access_ok(VERIFY_WRITE, buf, nbytes)) return -EFAULT; - /* enumerate busses */ down (&usb_bus_list_lock); - list_for_each(buslist, &usb_bus_list) { - /* print devices for this bus */ - bus = list_entry(buslist, struct usb_bus, bus_list); - + /* print devices for all busses */ + list_for_each_entry(bus, &usb_bus_list, bus_list) { /* recurse through all children of the root hub */ if (!bus->root_hub) continue; -- cgit v1.2.3 From 094be4f389c0b5f9f619ba51933a1b6e7dc41424 Mon Sep 17 00:00:00 2001 From: Maximilian Attems Date: Sun, 5 Sep 2004 09:21:11 +0200 Subject: [PATCH] list_for_each_entry: drivers-usb-serial-ipaq.c Use list_for_each_entry_safe to make code more readable. Compile tested. Signed-off-by: Domen Puncer Signed-off-by: Maximilian Attems Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/ipaq.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/drivers/usb/serial/ipaq.c b/drivers/usb/serial/ipaq.c index 8fbce2732c51..00cb88dd67b6 100644 --- a/drivers/usb/serial/ipaq.c +++ b/drivers/usb/serial/ipaq.c @@ -419,9 +419,8 @@ static void ipaq_write_gather(struct usb_serial_port *port) struct ipaq_private *priv = usb_get_serial_port_data(port); struct usb_serial *serial = port->serial; int count, room; - struct ipaq_packet *pkt; + struct ipaq_packet *pkt, *tmp; struct urb *urb = port->write_urb; - struct list_head *tmp; if (urb->status == -EINPROGRESS) { /* Should never happen */ @@ -429,9 +428,7 @@ static void ipaq_write_gather(struct usb_serial_port *port) return; } room = URBDATA_SIZE; - for (tmp = priv->queue.next; tmp != &priv->queue;) { - pkt = list_entry(tmp, struct ipaq_packet, list); - tmp = tmp->next; + list_for_each_entry_safe(pkt, tmp, &priv->queue, list) { count = min(room, (int)(pkt->len - pkt->written)); memcpy(urb->transfer_buffer + (URBDATA_SIZE - room), pkt->data + pkt->written, count); @@ -503,22 +500,16 @@ static int ipaq_chars_in_buffer(struct usb_serial_port *port) static void ipaq_destroy_lists(struct usb_serial_port *port) { struct ipaq_private *priv = usb_get_serial_port_data(port); - struct list_head *tmp; - struct ipaq_packet *pkt; + struct ipaq_packet *pkt, *tmp; - for (tmp = priv->queue.next; tmp != &priv->queue;) { - pkt = list_entry(tmp, struct ipaq_packet, list); - tmp = tmp->next; + list_for_each_entry_safe(pkt, tmp, &priv->queue, list) { kfree(pkt->data); kfree(pkt); } - for (tmp = priv->freelist.next; tmp != &priv->freelist;) { - pkt = list_entry(tmp, struct ipaq_packet, list); - tmp = tmp->next; + list_for_each_entry_safe(pkt, tmp, &priv->freelist, list) { kfree(pkt->data); kfree(pkt); } - return; } -- cgit v1.2.3 From c0759750faffec450f8768c399632d62bb4b9c54 Mon Sep 17 00:00:00 2001 From: Maximilian Attems Date: Sun, 5 Sep 2004 09:21:45 +0200 Subject: [PATCH] list_for_each_entry: drivers-usb-host-hc_sl811.c Make code more readable with list_for_each_entry_safe. (Is this a non i386? I can't compile it.) Signed-off-by: Domen Puncer Signed-off-by: Maximilian Attems Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/hc_sl811.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/usb/host/hc_sl811.c b/drivers/usb/host/hc_sl811.c index b57f1fe8258d..baf0e8086352 100644 --- a/drivers/usb/host/hc_sl811.c +++ b/drivers/usb/host/hc_sl811.c @@ -1343,15 +1343,11 @@ static int __init hci_hcd_init (void) *****************************************************************/ static void __exit hci_hcd_cleanup (void) { - struct list_head *hci_l; - hci_t *hci; + hci_t *hci, *tmp; DBGFUNC ("Enter hci_hcd_cleanup\n"); - for (hci_l = hci_hcd_list.next; hci_l != &hci_hcd_list;) { - hci = list_entry (hci_l, hci_t, hci_hcd_list); - hci_l = hci_l->next; + list_for_each_entry_safe(hci, tmp, &hci_hcd_list, hci_hcd_list) hc_release_hci (hci); - } } module_init (hci_hcd_init); -- cgit v1.2.3 From a8422ce56102c2e0a96a2ffe3e9485a389e548ce Mon Sep 17 00:00:00 2001 From: Maximilian Attems Date: Sun, 5 Sep 2004 09:22:18 +0200 Subject: [PATCH] list_for_each_entry: drivers-usb-media-dabusb.c Use list_for_each_entry to make code more readable. Compile tested. Signed-off-by: Domen Puncer Signed-off-by: Maximilian Attems Signed-off-by: Greg Kroah-Hartman --- drivers/usb/media/dabusb.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/usb/media/dabusb.c b/drivers/usb/media/dabusb.c index 4cc6e57dfa30..0aae6ecc7ad6 100644 --- a/drivers/usb/media/dabusb.c +++ b/drivers/usb/media/dabusb.c @@ -109,16 +109,13 @@ static void dump_urb (struct urb *urb) static int dabusb_cancel_queue (pdabusb_t s, struct list_head *q) { unsigned long flags; - struct list_head *p; pbuff_t b; dbg("dabusb_cancel_queue"); spin_lock_irqsave (&s->lock, flags); - for (p = q->next; p != q; p = p->next) { - b = list_entry (p, buff_t, buff_list); - + list_for_each_entry(b, q, buff_list) { #ifdef DEBUG dump_urb(b->purb); #endif -- cgit v1.2.3 From ba3b76470a73294cded6b93b0bff89884ed90ea5 Mon Sep 17 00:00:00 2001 From: Maximilian Attems Date: Sun, 5 Sep 2004 09:22:50 +0200 Subject: [PATCH] list_for_each_entry: drivers-usb-class-usb-midi.c Make code more readable with list_for_each_entry. Compile tested. Signed-off-by: Domen Puncer Signed-off-by: Maximilian Attems Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/usb-midi.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/usb/class/usb-midi.c b/drivers/usb/class/usb-midi.c index a9dc047a04a7..c4192ea52269 100644 --- a/drivers/usb/class/usb-midi.c +++ b/drivers/usb/class/usb-midi.c @@ -805,7 +805,6 @@ static int usb_midi_open(struct inode *inode, struct file *file) { int minor = iminor(inode); DECLARE_WAITQUEUE(wait, current); - struct list_head *devs, *mdevs; struct usb_midi_state *s; struct usb_mididev *m; unsigned long flags; @@ -817,10 +816,8 @@ static int usb_midi_open(struct inode *inode, struct file *file) for(;;) { down(&open_sem); - list_for_each(devs, &mididevs) { - s = list_entry(devs, struct usb_midi_state, mididev); - list_for_each(mdevs, &s->midiDevList) { - m = list_entry(mdevs, struct usb_mididev, list); + list_for_each_entry(s, &mididevs, mididev) { + list_for_each_entry(m, &s->midiDevList, list) { if ( !((m->dev_midi ^ minor) & ~0xf) ) goto device_found; } @@ -1994,7 +1991,6 @@ static int usb_midi_probe(struct usb_interface *intf, static void usb_midi_disconnect(struct usb_interface *intf) { struct usb_midi_state *s = usb_get_intfdata (intf); - struct list_head *list; struct usb_mididev *m; if ( !s ) @@ -2012,8 +2008,7 @@ static void usb_midi_disconnect(struct usb_interface *intf) s->usbdev = NULL; usb_set_intfdata (intf, NULL); - list_for_each(list, &s->midiDevList) { - m = list_entry(list, struct usb_mididev, list); + list_for_each_entry(m, &s->midiDevList, list) { wake_up(&(m->min.ep->wait)); wake_up(&(m->mout.ep->wait)); if ( m->dev_midi >= 0 ) { -- cgit v1.2.3 From 1c94b8328631a97a1a93039ae95e2bc634c20327 Mon Sep 17 00:00:00 2001 From: Maximilian Attems Date: Sun, 5 Sep 2004 09:23:24 +0200 Subject: [PATCH] list_for_each_entry: drivers-usb-class-audio.c Make code more readable with list_for_each_entry. Compile tested. Signed-off-by: Domen Puncer Signed-off-by: Maximilian Attems Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/audio.c | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/drivers/usb/class/audio.c b/drivers/usb/class/audio.c index 17a6dd74713f..36b3f3663e62 100644 --- a/drivers/usb/class/audio.c +++ b/drivers/usb/class/audio.c @@ -1949,15 +1949,12 @@ static inline int prog_dmabuf_out(struct usb_audiodev *as) static int usb_audio_open_mixdev(struct inode *inode, struct file *file) { unsigned int minor = iminor(inode); - struct list_head *devs, *mdevs; struct usb_mixerdev *ms; struct usb_audio_state *s; down(&open_sem); - list_for_each(devs, &audiodevs) { - s = list_entry(devs, struct usb_audio_state, audiodev); - list_for_each(mdevs, &s->mixerlist) { - ms = list_entry(mdevs, struct usb_mixerdev, list); + list_for_each_entry(s, &audiodevs, audiodev) { + list_for_each_entry(ms, &s->mixerlist, list) { if (ms->dev_mixer == minor) goto mixer_found; } @@ -2634,16 +2631,13 @@ static int usb_audio_open(struct inode *inode, struct file *file) { unsigned int minor = iminor(inode); DECLARE_WAITQUEUE(wait, current); - struct list_head *devs, *adevs; struct usb_audiodev *as; struct usb_audio_state *s; for (;;) { down(&open_sem); - list_for_each(devs, &audiodevs) { - s = list_entry(devs, struct usb_audio_state, audiodev); - list_for_each(adevs, &s->audiolist) { - as = list_entry(adevs, struct usb_audiodev, list); + list_for_each_entry(s, &audiodevs, audiodev) { + list_for_each_entry(as, &s->audiolist, list) { if (!((as->dev_audio ^ minor) & ~0xf)) goto device_found; } @@ -3809,7 +3803,6 @@ static int usb_audio_probe(struct usb_interface *intf, static void usb_audio_disconnect(struct usb_interface *intf) { struct usb_audio_state *s = usb_get_intfdata (intf); - struct list_head *list; struct usb_audiodev *as; struct usb_mixerdev *ms; @@ -3831,8 +3824,7 @@ static void usb_audio_disconnect(struct usb_interface *intf) usb_set_intfdata (intf, NULL); /* deregister all audio and mixer devices, so no new processes can open this device */ - list_for_each(list, &s->audiolist) { - as = list_entry(list, struct usb_audiodev, list); + list_for_each_entry(as, &s->audiolist, list) { usbin_disc(as); usbout_disc(as); wake_up(&as->usbin.dma.wait); @@ -3843,8 +3835,7 @@ static void usb_audio_disconnect(struct usb_interface *intf) } as->dev_audio = -1; } - list_for_each(list, &s->mixerlist) { - ms = list_entry(list, struct usb_mixerdev, list); + list_for_each_entry(ms, &s->mixerlist, list) { if (ms->dev_mixer >= 0) { unregister_sound_mixer(ms->dev_mixer); printk(KERN_INFO "usbaudio: unregister mixer 14,%d\n", ms->dev_mixer); -- cgit v1.2.3 From 7a5baeee17b33ebd029aae01f8374f4798e37bb0 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Sun, 5 Sep 2004 09:53:15 +0200 Subject: [PATCH] USB: Centralize logical disconnects in the hub driver This patch takes some code that was only used in one place in the hub driver, and packages it up into a subroutine which is now called from several places. The routine does a logical disconnect -- for example, after issuing a port reset, if the device descriptors have changed (because of a firmware update perhaps) we logically disconnect the old device structure and create a new one. Or if a resume fails for any reason we can do the same thing. This touches some of David Brownell's suspend/resume code, obviously. (It also fixes a couple of small errors in there.) He has said it looks okay. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 59 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 7f72ee96c7fe..0ea93676b6e8 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -1384,7 +1384,6 @@ static int hub_port_disable(struct usb_device *hdev, int port) int ret; if (hdev->children[port]) { - /* FIXME need disconnect() for NOTATTACHED device */ usb_set_device_state(hdev->children[port], USB_STATE_NOTATTACHED); } @@ -1396,6 +1395,30 @@ static int hub_port_disable(struct usb_device *hdev, int port) return ret; } +/* + * Disable a port and mark a logical connnect-change event, so that some + * time later khubd will disconnect() any existing usb_device on the port + * and will re-enumerate if there actually is a device attached. + */ +static void hub_port_logical_disconnect(struct usb_device *hdev, int port) +{ + struct usb_hub *hub; + + dev_dbg(hubdev(hdev), "logical disconnect on port %d\n", port + 1); + hub_port_disable(hdev, port); + + hub = usb_get_intfdata(hdev->actconfig->interface[0]); + set_bit(port, hub->change_bits); + + spin_lock_irq(&hub_event_lock); + if (list_empty(&hub->event_list)) { + list_add_tail(&hub->event_list, &hub_event_list); + wake_up(&khubd_wait); + } + spin_unlock_irq(&hub_event_lock); +} + + #ifdef CONFIG_USB_SUSPEND /* @@ -1729,7 +1752,7 @@ hub_port_resume(struct usb_device *hdev, int port) } } if (status < 0) - status = hub_port_disable(hdev, port); + hub_port_logical_disconnect(hdev, port - 1); return status; } @@ -1869,11 +1892,11 @@ static int hub_resume(struct usb_interface *intf) status = hub_port_resume(hdev, port + 1); else { status = finish_port_resume(udev); - if (status < 0) - status = hub_port_disable(hdev, port); - if (status < 0) + if (status < 0) { dev_dbg(&intf->dev, "resume port %d --> %d\n", - port, status); + port + 1, status); + hub_port_logical_disconnect(hdev, port); + } } up(&udev->serialize); } @@ -2502,15 +2525,17 @@ static void hub_events(void) if (portchange & USB_PORT_STAT_C_SUSPEND) { clear_port_feature(hdev, i + 1, USB_PORT_FEAT_C_SUSPEND); - if (hdev->children[i]) + if (hdev->children[i]) { ret = remote_wakeup(hdev->children[i]); - else + if (ret < 0) + connect_change = 1; + } else { ret = -ENODEV; + hub_port_disable(hdev, i); + } dev_dbg (hub_dev, "resume on port %d, status %d\n", i + 1, ret); - if (ret < 0) - ret = hub_port_disable(hdev, i); } if (portchange & USB_PORT_STAT_C_OVERCURRENT) { @@ -2713,7 +2738,6 @@ int __usb_reset_device(struct usb_device *udev) struct usb_device *parent = udev->parent; struct usb_device_descriptor descriptor = udev->descriptor; int i, ret, port = -1; - struct usb_hub *hub; if (udev->state == USB_STATE_NOTATTACHED || udev->state == USB_STATE_SUSPENDED) { @@ -2794,18 +2818,7 @@ int __usb_reset_device(struct usb_device *udev) return 0; re_enumerate: - hub_port_disable(parent, port); - - hub = usb_get_intfdata(parent->actconfig->interface[0]); - set_bit(port, hub->change_bits); - - spin_lock_irq(&hub_event_lock); - if (list_empty(&hub->event_list)) { - list_add_tail(&hub->event_list, &hub_event_list); - wake_up(&khubd_wait); - } - spin_unlock_irq(&hub_event_lock); - + hub_port_logical_disconnect(parent, port); return -ENODEV; } EXPORT_SYMBOL(__usb_reset_device); -- cgit v1.2.3 From 8164fc747f79016db37c0f7bbe8fa98639f2c4f4 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 7 Sep 2004 19:44:25 -0700 Subject: [PATCH] USB: Internal port numbers start at 0 This patch changes a couple of new routines in the suspend/resume code. Internally they use port numbers starting from 1, unlike every other routine in the hub driver. This changes the port numbers to origin-0, for consistency's sake. Of course, messages sent to the system log will continue to start counting from 1. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 0ea93676b6e8..8c9f120ea9aa 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -1436,8 +1436,8 @@ static int hub_port_suspend(struct usb_device *hdev, int port) int status; struct usb_device *udev; - udev = hdev->children[port - 1]; - // dev_dbg(hubdev(hdev), "suspend port %d\n", port); + udev = hdev->children[port]; + // dev_dbg(hubdev(hdev), "suspend port %d\n", port + 1); /* enable remote wakeup when appropriate; this lets the device * wake up the upstream hub (including maybe the root hub). @@ -1462,11 +1462,11 @@ static int hub_port_suspend(struct usb_device *hdev, int port) } /* see 7.1.7.6 */ - status = set_port_feature(hdev, port, USB_PORT_FEAT_SUSPEND); + status = set_port_feature(hdev, port + 1, USB_PORT_FEAT_SUSPEND); if (status) { dev_dbg(hubdev(hdev), "can't suspend port %d, status %d\n", - port, status); + port + 1, status); /* paranoia: "should not happen" */ (void) usb_control_msg(udev, usb_sndctrlpipe(udev, 0), USB_REQ_CLEAR_FEATURE, USB_RECIP_DEVICE, @@ -1585,7 +1585,7 @@ static int __usb_suspend_device (struct usb_device *udev, int port, u32 state) else status = -EOPNOTSUPP; } else - status = hub_port_suspend(udev->parent, port + 1); + status = hub_port_suspend(udev->parent, port); if (status == 0) udev->dev.power.power_state = state; @@ -1710,15 +1710,15 @@ hub_port_resume(struct usb_device *hdev, int port) int status; struct usb_device *udev; - udev = hdev->children[port - 1]; - // dev_dbg(hubdev(hdev), "resume port %d\n", port); + udev = hdev->children[port]; + // dev_dbg(hubdev(hdev), "resume port %d\n", port + 1); /* see 7.1.7.7; affects power usage, but not budgeting */ - status = clear_port_feature(hdev, port, USB_PORT_FEAT_SUSPEND); + status = clear_port_feature(hdev, port + 1, USB_PORT_FEAT_SUSPEND); if (status) { dev_dbg(&hdev->actconfig->interface[0]->dev, "can't resume port %d, status %d\n", - port, status); + port + 1, status); } else { u16 devstatus; u16 portchange; @@ -1736,7 +1736,7 @@ hub_port_resume(struct usb_device *hdev, int port) * sequence. */ devstatus = portchange = 0; - status = hub_port_status(hdev, port - 1, + status = hub_port_status(hdev, port, &devstatus, &portchange); if (status < 0 || (devstatus & LIVE_FLAGS) != LIVE_FLAGS @@ -1744,7 +1744,7 @@ hub_port_resume(struct usb_device *hdev, int port) ) { dev_dbg(&hdev->actconfig->interface[0]->dev, "port %d status %04x.%04x after resume, %d\n", - port, portchange, devstatus, status); + port + 1, portchange, devstatus, status); } else { /* TRSMRCY = 10 msec */ msleep(10); @@ -1752,7 +1752,7 @@ hub_port_resume(struct usb_device *hdev, int port) } } if (status < 0) - hub_port_logical_disconnect(hdev, port - 1); + hub_port_logical_disconnect(hdev, port); return status; } @@ -1796,7 +1796,7 @@ int usb_resume_device(struct usb_device *udev) ->actconfig->interface[0]); } } else if (udev->state == USB_STATE_SUSPENDED) { - status = hub_port_resume(udev->parent, port + 1); + status = hub_port_resume(udev->parent, port); } else { status = 0; udev->dev.power.power_state = PM_SUSPEND_ON; @@ -1889,7 +1889,7 @@ static int hub_resume(struct usb_interface *intf) continue; down (&udev->serialize); if (portstat & USB_PORT_STAT_SUSPEND) - status = hub_port_resume(hdev, port + 1); + status = hub_port_resume(hdev, port); else { status = finish_port_resume(udev); if (status < 0) { -- cgit v1.2.3 From d78de54e39931e71c94f452f2b729f90e7243da7 Mon Sep 17 00:00:00 2001 From: Luca Risolia Date: Tue, 7 Sep 2004 21:05:50 -0700 Subject: [PATCH] USB: SN9C10x driver update Changes: - Add support for SN9C103 based devices. The audio capability is already supported but not released in this version. I will release it once someone donates two SN9C103 based devices. - Implement VIDIOC_G_CTRL for TAS5110C1B and TAS51130D1B - Replace "SN9C10[12]" strings with "SN9C10x" - Add red, green, blue gain controls to the SN9C103 - Memory offsets are now page-aligned - Fix typos in the documentation - Documentation updates - Setting bounds are checked by the core module - Add exposure control for TAS51130D1B Signed-off-by: Luca Risolia Signed-off-by: Greg Kroah-Hartman --- CREDITS | 2 +- Documentation/usb/sn9c102.txt | 100 +++++++++++++++++--------- MAINTAINERS | 2 +- drivers/usb/media/Kconfig | 6 +- drivers/usb/media/sn9c102.h | 15 ++-- drivers/usb/media/sn9c102_core.c | 124 ++++++++++++++++++++++++++------- drivers/usb/media/sn9c102_pas106b.c | 14 ++-- drivers/usb/media/sn9c102_pas202bcb.c | 12 ++-- drivers/usb/media/sn9c102_sensor.h | 58 ++++++++++----- drivers/usb/media/sn9c102_tas5110c1b.c | 45 +++++++++--- drivers/usb/media/sn9c102_tas5130d1b.c | 60 +++++++++++++--- 11 files changed, 317 insertions(+), 121 deletions(-) diff --git a/CREDITS b/CREDITS index 08f699ba8b2b..e6c482a46393 100644 --- a/CREDITS +++ b/CREDITS @@ -2718,7 +2718,7 @@ N: Luca Risolia E: luca.risolia@studio.unibo.it P: 1024D/FCE635A4 88E8 F32F 7244 68BA 3958 5D40 99DA 5D2A FCE6 35A4 D: V4L driver for W996[87]CF JPEG USB Dual Mode Camera Chips -D: V4L2 driver for SN9C10[12] PC Camera Controllers +D: V4L2 driver for SN9C10x PC Camera Controllers S: Via Liberta' 41/A S: Osio Sotto, 24046, Bergamo S: Italy diff --git a/Documentation/usb/sn9c102.txt b/Documentation/usb/sn9c102.txt index 3999069b94bc..ea4f4274b371 100644 --- a/Documentation/usb/sn9c102.txt +++ b/Documentation/usb/sn9c102.txt @@ -1,7 +1,7 @@ - SN9C10[12] PC Camera Controllers + SN9C10x PC Camera Controllers Driver for Linux - ================================ + ============================= - Documentation - @@ -49,22 +49,23 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 3. Overview =========== -This driver attempts to support the video streaming capabilities of the devices -mounting the SONiX SN9C101 or SONiX SN9C102 PC Camera Controllers. +This driver attempts to support the video and audio streaming capabilities of +the devices mounting the SONiX SN9C101, SN9C102 and SN9C103 (or SUI-102) PC +Camera Controllers. - It's worth to note that SONiX has never collaborated with me during the -development of this project, despite of several requests for enough detailed +development of this project, despite several requests for enough detailed specifications of the register tables, compression engine and video data format of the above chips - Up to 64 cameras can be handled at the same time. They can be connected and disconnected from the host many times without turning off the computer, if -your system supports the hotplug facility. +your system supports hotplugging. The driver relies on the Video4Linux2 and USB core modules. It has been designed to run properly on SMP systems as well. -The latest version of the SN9C10[12] driver can be found at the following URL: +The latest version of the SN9C10x driver can be found at the following URL: http://go.lamarinapunto.com/ @@ -150,17 +151,20 @@ Default: 2 7. Optional device control through "sysfs" ========================================== -It is possible to read and write both the SN9C10[12] and the image sensor +It is possible to read and write both the SN9C10x and the image sensor registers by using the "sysfs" filesystem interface. -Every time a supported device is recognized, a read-only file named "green" is +Every time a supported device is recognized, a write-only file named "green" is created in the /sys/class/video4linux/videoX directory. You can set the green channel's gain by writing the desired value to it. The value may range from 0 -to 15. +to 15 for SN9C101 or SN9C102 bridges, from 0 to 127 for SN9C103 bridges. +Similarly, only for SN9C103 controllers, blue and red gain control files are +available in the same directory, for which accepted values may range from 0 to +127. There are other four entries in the directory above for each registered camera: "reg", "val", "i2c_reg" and "i2c_val". The first two files control the -SN9C10[12] bridge, while the other two control the sensor chip. "reg" and +SN9C10x bridge, while the other two control the sensor chip. "reg" and "i2c_reg" hold the values of the current register index where the following reading/writing operations are addressed at through "val" and "i2c_val". Their use is not intended for end-users, unless you know what you are doing. Note @@ -169,19 +173,19 @@ support the standard I2C protocol. Also, remember that you must be logged in as root before writing to them. As an example, suppose we were to want to read the value contained in the -register number 1 of the sensor register table - which usually is the product +register number 1 of the sensor register table - which is usually the product identifier - of the camera registered as "/dev/video0": [root@localhost #] cd /sys/class/video4linux/video0 [root@localhost #] echo 1 > i2c_reg [root@localhost #] cat i2c_val -Now let's set the green gain's register of the SN9C10[12] chip to 2: +Now let's set the green gain's register of the SN9C10x chip to 2: [root@localhost #] echo 0x11 > reg [root@localhost #] echo 2 > val -Note that the SN9C10[12] always returns 0 when some of its registers are read. +Note that the SN9C10x always returns 0 when some of its registers are read. To avoid race conditions, all the I/O accesses to the files are serialized. @@ -192,25 +196,52 @@ here. They have never collaborated with me, so no advertising - From the point of view of a driver, what unambiguously identify a device are its vendor and product USB identifiers. Below is a list of known identifiers of -devices mounting the SN9C10[12] PC camera controllers: +devices mounting the SN9C10x PC camera controllers: Vendor ID Product ID --------- ---------- -0xc45 0x6001 -0xc45 0x6005 -0xc45 0x6009 -0xc45 0x600d -0xc45 0x6024 -0xc45 0x6025 -0xc45 0x6028 -0xc45 0x6029 -0xc45 0x602a -0xc45 0x602c -0xc45 0x6030 +0x0c45 0x6001 +0x0c45 0x6005 +0x0c45 0x6009 +0x0c45 0x600d +0x0c45 0x6024 +0x0c45 0x6025 +0x0c45 0x6028 +0x0c45 0x6029 +0x0c45 0x602a +0x0c45 0x602b +0x0c45 0x602c +0x0c45 0x6030 +0x0c45 0x6080 +0x0c45 0x6082 +0x0c45 0x6083 +0x0c45 0x6088 +0x0c45 0x608a +0x0c45 0x608b +0x0c45 0x608c +0x0c45 0x608e +0x0c45 0x608f +0x0c45 0x60a0 +0x0c45 0x60a2 +0x0c45 0x60a3 +0x0c45 0x60a8 +0x0c45 0x60aa +0x0c45 0x60ab +0x0c45 0x60ac +0x0c45 0x60ae +0x0c45 0x60af +0x0c45 0x60b0 +0x0c45 0x60b2 +0x0c45 0x60b3 +0x0c45 0x60b8 +0x0c45 0x60ba +0x0c45 0x60bb +0x0c45 0x60bc +0x0c45 0x60be The list above does NOT imply that all those devices work with this driver: up -until now only the ones that mount the following image sensors are supported. -Kernel messages will always tell you whether this is the case: +until now only the ones that mount the following image sensors are supported; +kernel messages will always tell you whether this is the case: Model Manufacturer ----- ------------ @@ -224,7 +255,7 @@ listed in the above table, you may try to add the specific USB VendorID and ProductID identifiers to the sn9c102_id_table[] in the file "sn9c102_sensor.h"; then compile, load the module again and look at the kernel output. If this works, please send an email to me reporting the kernel messages, so -that I will add a new entry in the list of supported devices. +that I can add a new entry in the list of supported devices. Donations of new models for further testing and support would be much appreciated. I won't add official support for hardware that I don't actually @@ -238,8 +269,8 @@ have created for this purpose, which is present in "sn9c102_sensor.h" (documentation is included there). As an example, have a look at the code in "sn9c102_pas106b.c", which uses the mentioned interface. -At the moment, not yet supported image sensors are: HV7131[D|E1] (VGA), -MI03 (VGA), OV7620 (VGA). +At the moment, possible unsupported image sensors are: HV7131x series (VGA), +MI03x series (VGA), OV7620 (VGA), OV7630 (VGA), CIS-VF10 (VGA). 10. Notes for V4L2 application developers @@ -254,12 +285,13 @@ device to switch to the other I/O method; - previously mapped buffer memory must always be unmapped before calling any of the "VIDIOC_S_CROP", "VIDIOC_TRY_FMT" and "VIDIOC_S_FMT" ioctl's. The same number of buffers as before will be allocated again to match the size of the -new video frames, so you have to map them again before any I/O attempts. +new video frames, so you have to map the buffers again before any I/O attempts +on them. Consistently with the hardware limits, this driver also supports image downscaling with arbitrary scaling factors from 1, 2 and 4 in both directions. -However the V4L2 API specifications don't correctly define how the scaling -factor can be choosen arbitrarily by the "negotiation" of the "source" and +However, the V4L2 API specifications don't correctly define how the scaling +factor can be chosen arbitrarily by the "negotiation" of the "source" and "target" rectangles. To work around this flaw, we have added the convention that, during the negotiation, whenever the "VIDIOC_S_CROP" ioctl is issued, the scaling factor is restored to 1. diff --git a/MAINTAINERS b/MAINTAINERS index 629e0a82aafd..09b549d488e1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2346,7 +2346,7 @@ L: linux-usb-devel@lists.sourceforge.net W: http://www.connecttech.com S: Supported -USB SN9C10[12] DRIVER +USB SN9C10x DRIVER P: Luca Risolia M: luca.risolia@studio.unibo.it L: linux-usb-devel@lists.sourceforge.net diff --git a/drivers/usb/media/Kconfig b/drivers/usb/media/Kconfig index 4ac7ac4f3b1d..678e5fcbe29a 100644 --- a/drivers/usb/media/Kconfig +++ b/drivers/usb/media/Kconfig @@ -123,11 +123,11 @@ config USB_SE401 module will be called se401. config USB_SN9C102 - tristate "USB SN9C10[12] PC Camera Controller support" + tristate "USB SN9C10x PC Camera Controller support" depends on USB && VIDEO_DEV ---help--- - Say Y here if you want support for cameras based on SONiX SN9C101 - or SN9C102 PC Camera Controllers. + Say Y here if you want support for cameras based on SONiX SN9C101, + SN9C102 or SN9C103 PC Camera Controllers. See for more informations. diff --git a/drivers/usb/media/sn9c102.h b/drivers/usb/media/sn9c102.h index 62ff2144392c..050a25d6f6aa 100644 --- a/drivers/usb/media/sn9c102.h +++ b/drivers/usb/media/sn9c102.h @@ -1,5 +1,5 @@ /*************************************************************************** - * V4L2 driver for SN9C10[12] PC Camera Controllers * + * V4L2 driver for SN9C10x PC Camera Controllers * * * * Copyright (C) 2004 by Luca Risolia * * * @@ -49,12 +49,18 @@ /*****************************************************************************/ -#define SN9C102_MODULE_NAME "V4L2 driver for SN9C10[12] PC Camera Controllers" +#define SN9C102_MODULE_NAME "V4L2 driver for SN9C10x PC Camera Controllers" #define SN9C102_MODULE_AUTHOR "(C) 2004 Luca Risolia" #define SN9C102_AUTHOR_EMAIL "" #define SN9C102_MODULE_LICENSE "GPL" -#define SN9C102_MODULE_VERSION "1:1.08" -#define SN9C102_MODULE_VERSION_CODE KERNEL_VERSION(1, 0, 8) +#define SN9C102_MODULE_VERSION "1:1.10" +#define SN9C102_MODULE_VERSION_CODE KERNEL_VERSION(1, 0, 10) + +enum sn9c102_bridge { + BRIDGE_SN9C101 = 0x01, + BRIDGE_SN9C102 = 0x02, + BRIDGE_SN9C103 = 0x04, +}; SN9C102_ID_TABLE; SN9C102_SENSOR_TABLE; @@ -105,6 +111,7 @@ struct sn9c102_device { struct video_device* v4ldev; + enum sn9c102_bridge bridge; struct sn9c102_sensor* sensor; struct usb_device* usbdev; diff --git a/drivers/usb/media/sn9c102_core.c b/drivers/usb/media/sn9c102_core.c index 9f775f74002c..3881ae5ea08f 100644 --- a/drivers/usb/media/sn9c102_core.c +++ b/drivers/usb/media/sn9c102_core.c @@ -1,5 +1,5 @@ /*************************************************************************** - * V4L2 driver for SN9C10[12] PC Camera Controllers * + * V4L2 driver for SN9C10x PC Camera Controllers * * * * Copyright (C) 2004 by Luca Risolia * * * @@ -169,15 +169,15 @@ static u32 sn9c102_request_buffers(struct sn9c102_device* cam, u32 count) cam->nbuffers = count; while (cam->nbuffers > 0) { - if ((buff = rvmalloc(cam->nbuffers * imagesize))) + if ((buff = rvmalloc(cam->nbuffers * PAGE_ALIGN(imagesize)))) break; cam->nbuffers--; } for (i = 0; i < cam->nbuffers; i++) { - cam->frame[i].bufmem = buff + i*imagesize; + cam->frame[i].bufmem = buff + i*PAGE_ALIGN(imagesize); cam->frame[i].buf.index = i; - cam->frame[i].buf.m.offset = i*imagesize; + cam->frame[i].buf.m.offset = i*PAGE_ALIGN(imagesize); cam->frame[i].buf.length = imagesize; cam->frame[i].buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; cam->frame[i].buf.sequence = 0; @@ -388,7 +388,7 @@ sn9c102_i2c_try_raw_write(struct sn9c102_device* cam, data[4] = data3; data[5] = data4; data[6] = data5; - data[7] = 0x10; + data[7] = 0x14; res = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x08, 0x41, 0x08, 0, data, 8, SN9C102_CTRL_TIMEOUT); if (res < 0) @@ -1022,15 +1022,79 @@ sn9c102_store_i2c_val(struct class_device* cd, const char* buf, size_t len) static ssize_t sn9c102_store_green(struct class_device* cd, const char* buf, size_t len) { + struct sn9c102_device* cam; + enum sn9c102_bridge bridge; ssize_t res = 0; u8 value; ssize_t count; + if (down_interruptible(&sn9c102_sysfs_lock)) + return -ERESTARTSYS; + + cam = video_get_drvdata(to_video_device(cd)); + if (!cam) { + up(&sn9c102_sysfs_lock); + return -ENODEV; + } + + bridge = cam->bridge; + + up(&sn9c102_sysfs_lock); + value = sn9c102_strtou8(buf, len, &count); - if (!count || value > 0x0f) + if (!count) return -EINVAL; - if ((res = sn9c102_store_reg(cd, "0x11", 4)) >= 0) + switch (bridge) { + case BRIDGE_SN9C101: + case BRIDGE_SN9C102: + if (value > 0x0f) + return -EINVAL; + if ((res = sn9c102_store_reg(cd, "0x11", 4)) >= 0) + res = sn9c102_store_val(cd, buf, len); + break; + case BRIDGE_SN9C103: + if (value > 0x7f) + return -EINVAL; + if ((res = sn9c102_store_reg(cd, "0x04", 4)) >= 0) + res = sn9c102_store_val(cd, buf, len); + break; + } + + return res; +} + + +static ssize_t +sn9c102_store_blue(struct class_device* cd, const char* buf, size_t len) +{ + ssize_t res = 0; + u8 value; + ssize_t count; + + value = sn9c102_strtou8(buf, len, &count); + if (!count || value > 0x7f) + return -EINVAL; + + if ((res = sn9c102_store_reg(cd, "0x06", 4)) >= 0) + res = sn9c102_store_val(cd, buf, len); + + return res; +} + + +static ssize_t +sn9c102_store_red(struct class_device* cd, const char* buf, size_t len) +{ + ssize_t res = 0; + u8 value; + ssize_t count; + + value = sn9c102_strtou8(buf, len, &count); + if (!count || value > 0x7f) + return -EINVAL; + + if ((res = sn9c102_store_reg(cd, "0x05", 4)) >= 0) res = sn9c102_store_val(cd, buf, len); return res; @@ -1046,6 +1110,8 @@ static CLASS_DEVICE_ATTR(i2c_reg, S_IRUGO | S_IWUSR, static CLASS_DEVICE_ATTR(i2c_val, S_IRUGO | S_IWUSR, sn9c102_show_i2c_val, sn9c102_store_i2c_val); static CLASS_DEVICE_ATTR(green, S_IWUGO, NULL, sn9c102_store_green); +static CLASS_DEVICE_ATTR(blue, S_IWUGO, NULL, sn9c102_store_blue); +static CLASS_DEVICE_ATTR(red, S_IWUGO, NULL, sn9c102_store_red); static void sn9c102_create_sysfs(struct sn9c102_device* cam) @@ -1054,7 +1120,12 @@ static void sn9c102_create_sysfs(struct sn9c102_device* cam) video_device_create_file(v4ldev, &class_device_attr_reg); video_device_create_file(v4ldev, &class_device_attr_val); - video_device_create_file(v4ldev, &class_device_attr_green); + if (cam->bridge == BRIDGE_SN9C101 || cam->bridge == BRIDGE_SN9C102) + video_device_create_file(v4ldev, &class_device_attr_green); + else if (cam->bridge == BRIDGE_SN9C103) { + video_device_create_file(v4ldev, &class_device_attr_blue); + video_device_create_file(v4ldev, &class_device_attr_red); + } if (cam->sensor->slave_write_id && cam->sensor->slave_read_id) { video_device_create_file(v4ldev, &class_device_attr_i2c_reg); video_device_create_file(v4ldev, &class_device_attr_i2c_val); @@ -1092,21 +1163,13 @@ static int sn9c102_set_crop(struct sn9c102_device* cam, struct v4l2_rect* rect) u8 h_start = (u8)(rect->left - s->cropcap.bounds.left), v_start = (u8)(rect->top - s->cropcap.bounds.top), h_size = (u8)(rect->width / 16), - v_size = (u8)(rect->height / 16), - ae_strx = 0x00, - ae_stry = 0x00, - ae_endx = h_size / 2, - ae_endy = v_size / 2; + v_size = (u8)(rect->height / 16); int err = 0; err += sn9c102_write_reg(cam, h_start, 0x12); err += sn9c102_write_reg(cam, v_start, 0x13); err += sn9c102_write_reg(cam, h_size, 0x15); err += sn9c102_write_reg(cam, v_size, 0x16); - err += sn9c102_write_reg(cam, ae_strx, 0x1c); - err += sn9c102_write_reg(cam, ae_stry, 0x1d); - err += sn9c102_write_reg(cam, ae_endx, 0x1e); - err += sn9c102_write_reg(cam, ae_endy, 0x1f); if (err) return -EIO; @@ -1636,16 +1699,21 @@ static int sn9c102_v4l2_ioctl(struct inode* inode, struct file* filp, if (copy_from_user(&ctrl, arg, sizeof(ctrl))) return -EFAULT; - if ((err = s->set_ctrl(cam, &ctrl))) - return err; - n = sizeof(s->qctrl) / sizeof(s->qctrl[0]); for (i = 0; i < n; i++) if (ctrl.id == s->qctrl[i].id) { - s->_qctrl[i].default_value = ctrl.value; + if (ctrl.value < s->qctrl[i].minimum || + ctrl.value > s->qctrl[i].maximum) + return -ERANGE; + ctrl.value -= ctrl.value % s->qctrl[i].step; break; } + if ((err = s->set_ctrl(cam, &ctrl))) + return err; + + s->_qctrl[i].default_value = ctrl.value; + return 0; } @@ -1776,7 +1844,7 @@ static int sn9c102_v4l2_ioctl(struct inode* inode, struct file* filp, DBG(1, "VIDIOC_S_CROP failed because of hardware " "problems. To use the camera, close and open " "/dev/video%d again.", cam->v4ldev->minor) - return err; + return -EIO; } s->pix_format.width = rect->width/scale; @@ -1951,7 +2019,7 @@ static int sn9c102_v4l2_ioctl(struct inode* inode, struct file* filp, DBG(1, "VIDIOC_S_FMT failed because of hardware " "problems. To use the camera, close and open " "/dev/video%d again.", cam->v4ldev->minor) - return err; + return -EIO; } memcpy(pfmt, pix, sizeof(*pix)); @@ -2286,15 +2354,17 @@ sn9c102_usb_probe(struct usb_interface* intf, const struct usb_device_id* id) r = sn9c102_read_reg(cam, 0x00); if (r < 0 || r != 0x10) { - DBG(1, "Sorry, this is not a SN9C10[12] based camera " + DBG(1, "Sorry, this is not a SN9C10x based camera " "(vid/pid 0x%04X/0x%04X)", sn9c102_id_table[i].idVendor,sn9c102_id_table[i].idProduct) err = -ENODEV; goto fail; } - DBG(2, "SN9C10[12] PC Camera Controller detected " - "(vid/pid 0x%04X/0x%04X)", + cam->bridge = (sn9c102_id_table[i].idProduct & 0xffc0) == 0x6080 ? + BRIDGE_SN9C102 : BRIDGE_SN9C103; + + DBG(2, "SN9C10x PC Camera Controller detected (vid/pid 0x%04X/0x%04X)", sn9c102_id_table[i].idVendor, sn9c102_id_table[i].idProduct) for (i = 0; sn9c102_sensor_table[i]; i++) { @@ -2318,7 +2388,7 @@ sn9c102_usb_probe(struct usb_interface* intf, const struct usb_device_id* id) cam->state |= DEV_MISCONFIGURED; } - strcpy(cam->v4ldev->name, "SN9C10[12] PC Camera"); + strcpy(cam->v4ldev->name, "SN9C10x PC Camera"); cam->v4ldev->owner = THIS_MODULE; cam->v4ldev->type = VID_TYPE_CAPTURE | VID_TYPE_SCALES; cam->v4ldev->hardware = VID_HARDWARE_SN9C102; diff --git a/drivers/usb/media/sn9c102_pas106b.c b/drivers/usb/media/sn9c102_pas106b.c index a302a4845893..8453409ea125 100644 --- a/drivers/usb/media/sn9c102_pas106b.c +++ b/drivers/usb/media/sn9c102_pas106b.c @@ -1,5 +1,5 @@ /*************************************************************************** - * Driver for PAS106B image sensor connected to the SN9C10[12] PC Camera * + * Driver for PAS106B image sensor connected to the SN9C10x PC Camera * * Controllers * * * * Copyright (C) 2004 by Luca Risolia * @@ -100,26 +100,26 @@ static int pas106b_set_ctrl(struct sn9c102_device* cam, switch (ctrl->id) { case V4L2_CID_RED_BALANCE: - err += sn9c102_i2c_write(cam, 0x0c, ctrl->value & 0x1f); + err += sn9c102_i2c_write(cam, 0x0c, ctrl->value); break; case V4L2_CID_BLUE_BALANCE: - err += sn9c102_i2c_write(cam, 0x09, ctrl->value & 0x1f); + err += sn9c102_i2c_write(cam, 0x09, ctrl->value); break; case V4L2_CID_GAIN: - err += sn9c102_i2c_write(cam, 0x0e, ctrl->value & 0x1f); + err += sn9c102_i2c_write(cam, 0x0e, ctrl->value); break; case V4L2_CID_BRIGHTNESS: - err += sn9c102_i2c_write(cam, 0x0d, 0x1f-(ctrl->value & 0x1f)); + err += sn9c102_i2c_write(cam, 0x0d, 0x1f - ctrl->value); break; case V4L2_CID_CONTRAST: - err += sn9c102_i2c_write(cam, 0x0f, ctrl->value & 0x03); + err += sn9c102_i2c_write(cam, 0x0f, ctrl->value); break; default: return -EINVAL; } err += sn9c102_i2c_write(cam, 0x13, 0x01); - return err; + return err ? -EIO : 0; } diff --git a/drivers/usb/media/sn9c102_pas202bcb.c b/drivers/usb/media/sn9c102_pas202bcb.c index 26944eaf85d0..ffd12ed14e34 100644 --- a/drivers/usb/media/sn9c102_pas202bcb.c +++ b/drivers/usb/media/sn9c102_pas202bcb.c @@ -1,5 +1,5 @@ /*************************************************************************** - * Driver for PAS202BCB image sensor connected to the SN9C10[12] PC Camera * + * Driver for PAS202BCB image sensor connected to the SN9C10x PC Camera * * Controllers * * * * Copyright (C) 2004 by Carlos Eduardo Medaglia Dyonisio * @@ -95,23 +95,23 @@ static int pas202bcb_set_ctrl(struct sn9c102_device* cam, switch (ctrl->id) { case V4L2_CID_RED_BALANCE: - err += sn9c102_i2c_write(cam, 0x09, ctrl->value & 0x0f); + err += sn9c102_i2c_write(cam, 0x09, ctrl->value); break; case V4L2_CID_BLUE_BALANCE: - err += sn9c102_i2c_write(cam, 0x07, ctrl->value & 0x0f); + err += sn9c102_i2c_write(cam, 0x07, ctrl->value); break; case V4L2_CID_GAIN: - err += sn9c102_i2c_write(cam, 0x10, ctrl->value & 0x1f); + err += sn9c102_i2c_write(cam, 0x10, ctrl->value); break; case V4L2_CID_BRIGHTNESS: - err += sn9c102_i2c_write(cam, 0x06, 0x0f-(ctrl->value & 0x0f)); + err += sn9c102_i2c_write(cam, 0x06, 0x0f - ctrl->value); break; default: return -EINVAL; } err += sn9c102_i2c_write(cam, 0x11, 0x01); - return err; + return err ? -EIO : 0; } diff --git a/drivers/usb/media/sn9c102_sensor.h b/drivers/usb/media/sn9c102_sensor.h index 3e7e4a2578bc..9632a5a63ccc 100644 --- a/drivers/usb/media/sn9c102_sensor.h +++ b/drivers/usb/media/sn9c102_sensor.h @@ -1,5 +1,5 @@ /*************************************************************************** - * API for image sensors connected to the SN9C10[12] PC Camera Controllers * + * API for image sensors connected to the SN9C10x PC Camera Controllers * * * * Copyright (C) 2004 by Luca Risolia * * * @@ -89,17 +89,44 @@ sn9c102_attach_sensor(struct sn9c102_device* cam, /* Each SN9C10X camera has proper PID/VID identifiers. Add them here in case.*/ #define SN9C102_ID_TABLE \ static const struct usb_device_id sn9c102_id_table[] = { \ - { USB_DEVICE(0xc45, 0x6001), }, /* TAS5110C1B */ \ - { USB_DEVICE(0xc45, 0x6005), }, /* TAS5110C1B */ \ - { USB_DEVICE(0xc45, 0x6009), }, /* PAS106B */ \ - { USB_DEVICE(0xc45, 0x600d), }, /* PAS106B */ \ - { USB_DEVICE(0xc45, 0x6024), }, \ - { USB_DEVICE(0xc45, 0x6025), }, /* TAS5130D1B and TAS5110C1B */ \ - { USB_DEVICE(0xc45, 0x6028), }, /* PAS202BCB */ \ - { USB_DEVICE(0xc45, 0x6029), }, /* PAS106B */ \ - { USB_DEVICE(0xc45, 0x602a), }, /* HV7131[D|E1] */ \ - { USB_DEVICE(0xc45, 0x602c), }, /* OV7620 */ \ - { USB_DEVICE(0xc45, 0x6030), }, /* MI03 */ \ + { USB_DEVICE(0x0c45, 0x6001), }, /* TAS5110C1B */ \ + { USB_DEVICE(0x0c45, 0x6005), }, /* TAS5110C1B */ \ + { USB_DEVICE(0x0c45, 0x6009), }, /* PAS106B */ \ + { USB_DEVICE(0x0c45, 0x600d), }, /* PAS106B */ \ + { USB_DEVICE(0x0c45, 0x6024), }, \ + { USB_DEVICE(0x0c45, 0x6025), }, /* TAS5130D1B and TAS5110C1B */ \ + { USB_DEVICE(0x0c45, 0x6028), }, /* PAS202BCB */ \ + { USB_DEVICE(0x0c45, 0x6029), }, /* PAS106B */ \ + { USB_DEVICE(0x0c45, 0x602a), }, /* HV7131[D|E1] */ \ + { USB_DEVICE(0x0c45, 0x602b), }, \ + { USB_DEVICE(0x0c45, 0x602c), }, /* OV7620 */ \ + { USB_DEVICE(0x0c45, 0x6030), }, /* MI03x */ \ + { USB_DEVICE(0x0c45, 0x6080), }, \ + { USB_DEVICE(0x0c45, 0x6082), }, /* MI0343 and MI0360 */ \ + { USB_DEVICE(0x0c45, 0x6083), }, /* HV7131[D|E1] */ \ + { USB_DEVICE(0x0c45, 0x6088), }, \ + { USB_DEVICE(0x0c45, 0x608a), }, \ + { USB_DEVICE(0x0c45, 0x608b), }, \ + { USB_DEVICE(0x0c45, 0x608c), }, /* HV7131x */ \ + { USB_DEVICE(0x0c45, 0x608e), }, /* CIS-VF10 */ \ + { USB_DEVICE(0x0c45, 0x608f), }, /* OV7630 */ \ + { USB_DEVICE(0x0c45, 0x60a0), }, \ + { USB_DEVICE(0x0c45, 0x60a2), }, \ + { USB_DEVICE(0x0c45, 0x60a3), }, \ + { USB_DEVICE(0x0c45, 0x60a8), }, /* PAS106B */ \ + { USB_DEVICE(0x0c45, 0x60aa), }, /* TAS5130D1B */ \ + { USB_DEVICE(0x0c45, 0x60ab), }, /* TAS5110C1B */ \ + { USB_DEVICE(0x0c45, 0x60ac), }, \ + { USB_DEVICE(0x0c45, 0x60ae), }, \ + { USB_DEVICE(0x0c45, 0x60af), }, /* PAS202BCB */ \ + { USB_DEVICE(0x0c45, 0x60b0), }, \ + { USB_DEVICE(0x0c45, 0x60b2), }, \ + { USB_DEVICE(0x0c45, 0x60b3), }, \ + { USB_DEVICE(0x0c45, 0x60b8), }, \ + { USB_DEVICE(0x0c45, 0x60ba), }, \ + { USB_DEVICE(0x0c45, 0x60bb), }, \ + { USB_DEVICE(0x0c45, 0x60bc), }, \ + { USB_DEVICE(0x0c45, 0x60be), }, \ { } \ }; @@ -173,9 +200,7 @@ struct sn9c102_sensor { /* These identifiers must be provided if the image sensor implements - the standard I2C protocol. TASC sensors don't, although they have a - serial interface: so this is a case where the "raw" I2C version - could be helpful. + the standard I2C protocol. */ u8 slave_read_id, slave_write_id; /* reg. 0x09 */ @@ -214,7 +239,8 @@ struct sn9c102_sensor { the list above. The returned value must follow the V4L2 specifications for the VIDIOC_G|C_CTRL ioctls. V4L2_CID_H|VCENTER are not supported by this driver, so do not implement them. Also, - passed values are NOT checked to see if they are out of bounds. + you don't have to check whether the passed values are out of bounds, + given that this is done by the core module. */ struct v4l2_cropcap cropcap; diff --git a/drivers/usb/media/sn9c102_tas5110c1b.c b/drivers/usb/media/sn9c102_tas5110c1b.c index 68e1b2e0ce18..006a6b569f4a 100644 --- a/drivers/usb/media/sn9c102_tas5110c1b.c +++ b/drivers/usb/media/sn9c102_tas5110c1b.c @@ -1,6 +1,6 @@ /*************************************************************************** - * Driver for TAS5110C1B image sensor connected to the SN9C10[12] PC * - * Camera Controllers * + * Driver for TAS5110C1B image sensor connected to the SN9C10x PC Camera * + * Controllers * * * * Copyright (C) 2004 by Luca Risolia * * * @@ -24,6 +24,8 @@ static struct sn9c102_sensor tas5110c1b; +static struct v4l2_control tas5110c1b_gain; + static int tas5110c1b_init(struct sn9c102_device* cam) { @@ -38,25 +40,42 @@ static int tas5110c1b_init(struct sn9c102_device* cam) err += sn9c102_write_reg(cam, 0x06, 0x18); err += sn9c102_write_reg(cam, 0xfb, 0x19); - err += sn9c102_i2c_try_raw_write(cam, &tas5110c1b, 4, 0x11, 0x00, 0xc0, - 0x80, 0, 0); + err += sn9c102_i2c_write(cam, 0xc0, 0x80); return err; } +static int tas5110c1b_get_ctrl(struct sn9c102_device* cam, + struct v4l2_control* ctrl) +{ + switch (ctrl->id) { + case V4L2_CID_GAIN: + ctrl->value = tas5110c1b_gain.value; + break; + default: + return -EINVAL; + } + + return 0; +} + + static int tas5110c1b_set_ctrl(struct sn9c102_device* cam, const struct v4l2_control* ctrl) { + int err = 0; + switch (ctrl->id) { case V4L2_CID_GAIN: - return sn9c102_i2c_try_raw_write(cam, &tas5110c1b, 4, 0x11, - 0x02, 0x20, - 0xff - (ctrl->value & 0xff), - 0, 0); + if (!(err += sn9c102_i2c_write(cam, 0x20, 0xf6 - ctrl->value))) + tas5110c1b_gain.value = ctrl->value; + break; default: return -EINVAL; } + + return err ? -EIO : 0; } @@ -85,6 +104,8 @@ static struct sn9c102_sensor tas5110c1b = { .maintainer = "Luca Risolia ", .frequency = SN9C102_I2C_100KHZ, .interface = SN9C102_I2C_3WIRES, + .slave_read_id = 0xff, /* fictitious */ + .slave_write_id = 0xff, /* fictitious */ .init = &tas5110c1b_init, .qctrl = { { @@ -92,9 +113,9 @@ static struct sn9c102_sensor tas5110c1b = { .type = V4L2_CTRL_TYPE_INTEGER, .name = "global gain", .minimum = 0x00, - .maximum = 0xff, + .maximum = 0xf6, .step = 0x01, - .default_value = 0x48, + .default_value = 0x40, .flags = 0, }, }, @@ -113,6 +134,7 @@ static struct sn9c102_sensor tas5110c1b = { .height = 288, }, }, + .get_ctrl = &tas5110c1b_get_ctrl, .set_crop = &tas5110c1b_set_crop, .pix_format = { .width = 352, @@ -130,7 +152,8 @@ int sn9c102_probe_tas5110c1b(struct sn9c102_device* cam) /* At the moment, sensor detection is based on USB pid/vid */ if (tas5110c1b.usbdev->descriptor.idProduct != 0x6001 && - tas5110c1b.usbdev->descriptor.idProduct != 0x6005) + tas5110c1b.usbdev->descriptor.idProduct != 0x6005 && + tas5110c1b.usbdev->descriptor.idProduct != 0x60ab) return -ENODEV; return 0; diff --git a/drivers/usb/media/sn9c102_tas5130d1b.c b/drivers/usb/media/sn9c102_tas5130d1b.c index 0bab19435399..7af73acd2c37 100644 --- a/drivers/usb/media/sn9c102_tas5130d1b.c +++ b/drivers/usb/media/sn9c102_tas5130d1b.c @@ -1,6 +1,6 @@ /*************************************************************************** - * Driver for TAS5130D1B image sensor connected to the SN9C10[12] PC * - * Camera Controllers * + * Driver for TAS5130D1B image sensor connected to the SN9C10x PC Camera * + * Controllers * * * * Copyright (C) 2004 by Luca Risolia * * * @@ -24,6 +24,8 @@ static struct sn9c102_sensor tas5130d1b; +static struct v4l2_control tas5130d1b_gain, tas5130d1b_exposure; + static int tas5130d1b_init(struct sn9c102_device* cam) { @@ -38,25 +40,47 @@ static int tas5130d1b_init(struct sn9c102_device* cam) err += sn9c102_write_reg(cam, 0x60, 0x17); err += sn9c102_write_reg(cam, 0x07, 0x18); - err += sn9c102_i2c_try_raw_write(cam, &tas5130d1b, 4, 0x11, 0x00, 0x40, - 0x47, 0, 0); - return err; } +static int tas5130d1b_get_ctrl(struct sn9c102_device* cam, + struct v4l2_control* ctrl) +{ + switch (ctrl->id) { + case V4L2_CID_GAIN: + ctrl->value = tas5130d1b_gain.value; + break; + case V4L2_CID_EXPOSURE: + ctrl->value = tas5130d1b_exposure.value; + break; + default: + return -EINVAL; + } + + return 0; +} + + static int tas5130d1b_set_ctrl(struct sn9c102_device* cam, const struct v4l2_control* ctrl) { + int err = 0; + switch (ctrl->id) { case V4L2_CID_GAIN: - return sn9c102_i2c_try_raw_write(cam, &tas5130d1b, 4, 0x11, - 0x02, 0x20, - 0xff - (ctrl->value & 0xff), - 0, 0); + if (!(err += sn9c102_i2c_write(cam, 0x20, 0xf6 - ctrl->value))) + tas5130d1b_gain.value = ctrl->value; + break; + case V4L2_CID_EXPOSURE: + if (!(err += sn9c102_i2c_write(cam, 0x40, 0x47 - ctrl->value))) + tas5130d1b_exposure.value = ctrl->value; + break; default: return -EINVAL; } + + return err ? -EIO : 0; } @@ -85,6 +109,8 @@ static struct sn9c102_sensor tas5130d1b = { .maintainer = "Luca Risolia ", .frequency = SN9C102_I2C_100KHZ, .interface = SN9C102_I2C_3WIRES, + .slave_read_id = 0xff, /* fictitious */ + .slave_write_id = 0xff, /* fictitious */ .init = &tas5130d1b_init, .qctrl = { { @@ -92,12 +118,23 @@ static struct sn9c102_sensor tas5130d1b = { .type = V4L2_CTRL_TYPE_INTEGER, .name = "global gain", .minimum = 0x00, - .maximum = 0xff, + .maximum = 0xf6, + .step = 0x02, + .default_value = 0x00, + .flags = 0, + }, + { + .id = V4L2_CID_EXPOSURE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "exposure", + .minimum = 0x00, + .maximum = 0x47, .step = 0x01, .default_value = 0x00, .flags = 0, }, }, + .get_ctrl = &tas5130d1b_get_ctrl, .set_ctrl = &tas5130d1b_set_ctrl, .cropcap = { .bounds = { @@ -129,7 +166,8 @@ int sn9c102_probe_tas5130d1b(struct sn9c102_device* cam) sn9c102_attach_sensor(cam, &tas5130d1b); /* At the moment, sensor detection is based on USB pid/vid */ - if (tas5130d1b.usbdev->descriptor.idProduct != 0x6025) + if (tas5130d1b.usbdev->descriptor.idProduct != 0x6025 && + tas5130d1b.usbdev->descriptor.idProduct != 0x60aa) return -ENODEV; return 0; -- cgit v1.2.3 From 1321d5f1a3d6d7618df0c933024b6ccc1f7c5be6 Mon Sep 17 00:00:00 2001 From: David Brownell Date: Tue, 7 Sep 2004 21:08:27 -0700 Subject: [PATCH] USB: EHCI SMP fix This addresses an SMP-only issue with the EHCI driver, where only one CPU should scan the schedule at a time (scanning is not re-entrant) but either the IRQ handler or a watchdog timer could end up starting it. Many thanks to Olaf Hering for isolating the failure mode, and testing this fix! Once once CPU starts scanning, any other might as well finish right away. This fix just adds a flag to detect that case. Signed-off-by: David Brownell Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-hcd.c | 9 +++++++++ drivers/usb/host/ehci.h | 1 + 2 files changed, 10 insertions(+) diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 554af349d5e2..2f204b864a90 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -695,9 +695,18 @@ static void ehci_work (struct ehci_hcd *ehci, struct pt_regs *regs) timer_action_done (ehci, TIMER_IO_WATCHDOG); if (ehci->reclaim_ready) end_unlink_async (ehci, regs); + + /* another CPU may drop ehci->lock during a schedule scan while + * it reports urb completions. this flag guards against bogus + * attempts at re-entrant schedule scanning. + */ + if (ehci->scanning) + return; + ehci->scanning = 1; scan_async (ehci, regs); if (ehci->next_uframe != -1) scan_periodic (ehci, regs); + ehci->scanning = 0; /* the IO watchdog guards against hardware or driver bugs that * misplace IRQs, and should let us run completely without IRQs. diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 14e001d98f57..de666f6b3314 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -53,6 +53,7 @@ struct ehci_hcd { /* one per controller */ struct ehci_qh *async; struct ehci_qh *reclaim; unsigned reclaim_ready : 1; + unsigned scanning : 1; /* periodic schedule support */ #define DEFAULT_I_TDPS 1024 /* some HCs can do less */ -- cgit v1.2.3 From 3a18409b10d6759e80715b690c9da8d8e7c0c7fc Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Tue, 7 Sep 2004 21:18:37 -0700 Subject: [PATCH] USB: Codemercs IO-Warrior support From: Steffen Zieger Here is a patch to get the kernel module from Codemerces to work. The module is available in source for the 2.4 and 2.6 series except the needed changes in hid-core.c. Codemercs distribute the needed changes as a complete file (version 2.6.4). This isn't working with 2.6.8.1. http://www.codemercs.com Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- drivers/usb/input/hid-core.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/usb/input/hid-core.c b/drivers/usb/input/hid-core.c index 1b94a27f2eb4..34b0e91aeb1b 100644 --- a/drivers/usb/input/hid-core.c +++ b/drivers/usb/input/hid-core.c @@ -1439,6 +1439,12 @@ void hid_init_reports(struct hid_device *hid) #define USB_DEVICE_ID_1_PHIDGETSERVO_20 0x8101 #define USB_DEVICE_ID_4_PHIDGETSERVO_20 0x8104 +#define USB_VENDOR_ID_CODEMERCS 0x07c0 +#define USB_DEVICE_ID_CODEMERCS_IOW40 0x1500 +#define USB_DEVICE_ID_CODEMERCS_IOW24 0x1501 +#define USB_DEVICE_ID_CODEMERCS_IOW48 0x1502 +#define USB_DEVICE_ID_CODEMERCS_IOW28 0x1503 + static struct hid_blacklist { __u16 idVendor; __u16 idProduct; @@ -1521,6 +1527,11 @@ static struct hid_blacklist { { USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RUMBLEPAD, HID_QUIRK_BADPAD }, { USB_VENDOR_ID_TOPMAX, USB_DEVICE_ID_TOPMAX_COBRAPAD, HID_QUIRK_BADPAD }, + { USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW40, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW24, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW48, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW28, HID_QUIRK_IGNORE }, + { 0, 0 } }; -- cgit v1.2.3 From e9bd0a314ab6182cef28768edc6abffaafe3dc57 Mon Sep 17 00:00:00 2001 From: Catalin Boie Date: Tue, 7 Sep 2004 21:22:56 -0700 Subject: [PATCH] USB Serial: Correct a use of out of range variable Bug found by Coverity: http://linuxbugs.coverity.com/external/editbugparent.php?viewbugid=2137&checkers%5B%5D=all&status%5B%5D=BUG&status%5B%5D=UNINSPECTED&status%5B%5D=UNKNOWN&status%5B%5D=DON%27T%20CARE&status%5B%5D=PENDING&product%5B%5D=all&component%5B%5D=all&file=&fn=&sortby=reverse_rank&before=&after=&curpage=2&bugid=-1&comment=&reason= Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/usb-serial.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index 0e59c134134c..fd6bb67c69c1 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -388,7 +388,7 @@ static struct usb_serial *get_free_serial (struct usb_serial *serial, int num_po good_spot = 1; for (j = 1; j <= num_ports-1; ++j) - if ((serial_table[i+j]) || (i+j >= SERIAL_TTY_MINORS)) { + if ((i+j >= SERIAL_TTY_MINORS) || (serial_table[i+j])) { good_spot = 0; i += j; break; -- cgit v1.2.3 From 3aaad15ad14b56feec341f809d82c19fef59b302 Mon Sep 17 00:00:00 2001 From: Matthew Dharm Date: Tue, 7 Sep 2004 21:23:33 -0700 Subject: [PATCH] USB Storage: change how INQUIRY is fixed up The usb-storage driver 'fixes up' the INQUIRY data returned by a device so that it reads SCSI rev 2 by intercepting the data in-flight. This was done to make various SCSI drivers (sd, sr, etc.) work with the device. However, this technique also has the unfortunate side-effect that nobody can see the real rev. -- not even sg users. This patch changes that. Now, the SCSI revision is changed in the slave_configure() function. Thus, the 'real' data is available to anyone who wants to issue an INQUIRY directly via any means. This also simplifies the code somewhat. Signed-off-by: Matthew Dharm Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/isd200.c | 6 ------ drivers/usb/storage/protocol.c | 49 ------------------------------------------ drivers/usb/storage/scsiglue.c | 17 +++++++++++++++ 3 files changed, 17 insertions(+), 55 deletions(-) diff --git a/drivers/usb/storage/isd200.c b/drivers/usb/storage/isd200.c index a6aa4a918d1e..b6db57596e96 100644 --- a/drivers/usb/storage/isd200.c +++ b/drivers/usb/storage/isd200.c @@ -1052,12 +1052,6 @@ static int isd200_get_inquiry_data( struct us_data *us ) /* Standard IDE interface only supports disks */ info->InquiryData.DeviceType = DIRECT_ACCESS_DEVICE; - /* Fix-up the return data from an INQUIRY command to show - * ANSI SCSI rev 2 so we don't confuse the SCSI layers above us - * in Linux. - */ - info->InquiryData.Versions = 0x2; - /* The length must be at least 36 (5 + 31) */ info->InquiryData.AdditionalLength = 0x1F; diff --git a/drivers/usb/storage/protocol.c b/drivers/usb/storage/protocol.c index 5141c508e3a1..2836c1735d9e 100644 --- a/drivers/usb/storage/protocol.c +++ b/drivers/usb/storage/protocol.c @@ -57,38 +57,6 @@ * Helper routines ***********************************************************************/ -/* - * Fix-up the return data from an INQUIRY command to show - * ANSI SCSI rev 2 so we don't confuse the SCSI layers above us - */ -static void fix_inquiry_data(struct scsi_cmnd *srb) -{ - unsigned char databuf[3]; - unsigned int index, offset; - - /* verify that it's an INQUIRY command */ - if (srb->cmnd[0] != INQUIRY) - return; - - index = offset = 0; - if (usb_stor_access_xfer_buf(databuf, sizeof(databuf), srb, - &index, &offset, FROM_XFER_BUF) != sizeof(databuf)) - return; - - if ((databuf[2] & 7) == 2) - return; - - US_DEBUGP("Fixing INQUIRY data to show SCSI rev 2 - was %d\n", - databuf[2] & 7); - - /* Change the SCSI revision number */ - databuf[2] = (databuf[2] & ~7) | 2; - - index = offset = 0; - usb_stor_access_xfer_buf(databuf, sizeof(databuf), srb, - &index, &offset, TO_XFER_BUF); -} - /* * Fix-up the return data from a READ CAPACITY command. My Feiya reader * returns a value that is 1 too large. @@ -137,10 +105,6 @@ void usb_stor_qic157_command(struct scsi_cmnd *srb, struct us_data *us) /* send the command to the transport layer */ usb_stor_invoke_transport(srb, us); - if (srb->result == SAM_STAT_GOOD) { - /* fix the INQUIRY data if necessary */ - fix_inquiry_data(srb); - } } void usb_stor_ATAPI_command(struct scsi_cmnd *srb, struct us_data *us) @@ -160,11 +124,6 @@ void usb_stor_ATAPI_command(struct scsi_cmnd *srb, struct us_data *us) /* send the command to the transport layer */ usb_stor_invoke_transport(srb, us); - - if (srb->result == SAM_STAT_GOOD) { - /* fix the INQUIRY data if necessary */ - fix_inquiry_data(srb); - } } @@ -208,11 +167,6 @@ void usb_stor_ufi_command(struct scsi_cmnd *srb, struct us_data *us) /* send the command to the transport layer */ usb_stor_invoke_transport(srb, us); - - if (srb->result == SAM_STAT_GOOD) { - /* Fix the data for an INQUIRY, if necessary */ - fix_inquiry_data(srb); - } } void usb_stor_transparent_scsi_command(struct scsi_cmnd *srb, @@ -222,9 +176,6 @@ void usb_stor_transparent_scsi_command(struct scsi_cmnd *srb, usb_stor_invoke_transport(srb, us); if (srb->result == SAM_STAT_GOOD) { - /* Fix the INQUIRY data if necessary */ - fix_inquiry_data(srb); - /* Fix the READ CAPACITY result if necessary */ if (us->flags & US_FL_FIX_CAPACITY) fix_read_capacity(srb); diff --git a/drivers/usb/storage/scsiglue.c b/drivers/usb/storage/scsiglue.c index c18cd06ea31c..29f4e59759e6 100644 --- a/drivers/usb/storage/scsiglue.c +++ b/drivers/usb/storage/scsiglue.c @@ -98,6 +98,23 @@ static int slave_configure(struct scsi_device *sdev) * the end, scatter-gather buffers follow page boundaries. */ blk_queue_dma_alignment(sdev->request_queue, (512 - 1)); + /* Set the SCSI level to at least 2. We'll leave it at 3 if that's + * what is originally reported. We need this to avoid confusing + * the SCSI layer with devices that report 0 or 1, but need 10-byte + * commands (ala ATAPI devices behind certain bridges, or devices + * which simply have broken INQUIRY data). + * + * NOTE: This means /dev/sg programs (ala cdrecord) will get the + * actual information. This seems to be the preference for + * programs like that. + * + * NOTE: This also means that /proc/scsi/scsi and sysfs may report + * the actual value or the modified one, depending on where the + * data comes from. + */ + if (sdev->scsi_level < SCSI_2) + sdev->scsi_level = SCSI_2; + /* According to the technical support people at Genesys Logic, * devices using their chips have problems transferring more than * 32 KB at a time. In practice people have found that 64 KB -- cgit v1.2.3 From 0aa13ba4244b20daa66c2c9a32c195a166e9619c Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 8 Sep 2004 19:49:54 -0700 Subject: USB: make usb_unlink_urb() message only show up if CONFIG_DEBUG_KERNEL is enabled. Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/urb.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/core/urb.c b/drivers/usb/core/urb.c index 376e0e939d00..f57de4dca2bf 100644 --- a/drivers/usb/core/urb.c +++ b/drivers/usb/core/urb.c @@ -451,9 +451,11 @@ int usb_unlink_urb(struct urb *urb) if (!urb) return -EINVAL; if (!(urb->transfer_flags & URB_ASYNC_UNLINK)) { +#ifdef CONFIG_DEBUG_KERNEL printk(KERN_NOTICE "usb_unlink_urb() is deprecated for " - "synchronous unlinks. Use usb_kill_urb()\n"); + "synchronous unlinks. Use usb_kill_urb() instead.\n"); WARN_ON(1); +#endif usb_kill_urb(urb); return 0; } -- cgit v1.2.3 From ecabf237ffd4f70f656096120ce22b1e1b62b323 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Wed, 8 Sep 2004 20:03:05 -0700 Subject: [PATCH] USB: Add OTG support to g_file_storage This patch adds minimal USB On-The-Go support (mainly just an extra descriptor) to the File-Storage Gadget. The changes were based on the additions made to the Gadget Zero driver. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/file_storage.c | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/drivers/usb/gadget/file_storage.c b/drivers/usb/gadget/file_storage.c index 6e8008fa43cb..1280a7cfacf4 100644 --- a/drivers/usb/gadget/file_storage.c +++ b/drivers/usb/gadget/file_storage.c @@ -248,7 +248,7 @@ #define DRIVER_DESC "File-backed Storage Gadget" #define DRIVER_NAME "g_file_storage" -#define DRIVER_VERSION "28 July 2004" +#define DRIVER_VERSION "31 August 2004" static const char longname[] = DRIVER_DESC; static const char shortname[] = DRIVER_NAME; @@ -866,6 +866,14 @@ config_desc = { .bMaxPower = 1, // self-powered }; +static struct usb_otg_descriptor +otg_desc = { + .bLength = sizeof(otg_desc), + .bDescriptorType = USB_DT_OTG, + + .bmAttributes = USB_OTG_SRP, +}; + /* There is only one interface. */ static struct usb_interface_descriptor @@ -914,6 +922,7 @@ fs_intr_in_desc = { }; static const struct usb_descriptor_header *fs_function[] = { + (struct usb_descriptor_header *) &otg_desc, (struct usb_descriptor_header *) &intf_desc, (struct usb_descriptor_header *) &fs_bulk_in_desc, (struct usb_descriptor_header *) &fs_bulk_out_desc, @@ -976,6 +985,7 @@ hs_intr_in_desc = { }; static const struct usb_descriptor_header *hs_function[] = { + (struct usb_descriptor_header *) &otg_desc, (struct usb_descriptor_header *) &intf_desc, (struct usb_descriptor_header *) &hs_bulk_in_desc, (struct usb_descriptor_header *) &hs_bulk_out_desc, @@ -1018,9 +1028,10 @@ static struct usb_gadget_strings stringtab = { * and with code managing interfaces and their altsettings. They must * also handle different speeds and other-speed requests. */ -static int populate_config_buf(enum usb_device_speed speed, +static int populate_config_buf(struct usb_gadget *gadget, u8 *buf, u8 type, unsigned index) { + enum usb_device_speed speed = gadget->speed; int len; const struct usb_descriptor_header **function; @@ -1036,6 +1047,10 @@ static int populate_config_buf(enum usb_device_speed speed, #endif function = fs_function; + /* for now, don't advertise srp-only devices */ + if (!gadget->is_otg) + function++; + len = usb_gadget_config_buf(&config_desc, buf, EP0_BUFSIZE, function); ((struct usb_config_descriptor *) buf)->bDescriptorType = type; return len; @@ -1366,7 +1381,7 @@ static int standard_setup_req(struct fsg_dev *fsg, #ifdef CONFIG_USB_GADGET_DUALSPEED get_config: #endif - value = populate_config_buf(fsg->gadget->speed, + value = populate_config_buf(fsg->gadget, req->buf, ctrl->wValue >> 8, ctrl->wValue & 0xff); @@ -3896,6 +3911,11 @@ static int __init fsg_bind(struct usb_gadget *gadget) hs_intr_in_desc.bEndpointAddress = fs_intr_in_desc.bEndpointAddress; #endif + if (gadget->is_otg) { + otg_desc.bmAttributes |= USB_OTG_HNP, + config_desc.bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + rc = -ENOMEM; /* Allocate the request and buffer for endpoint 0 */ -- cgit v1.2.3 From af084fbb3d2144ef38d7b260de188bac25181610 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Wed, 8 Sep 2004 20:03:34 -0700 Subject: [PATCH] USB: New submission procedure for unusual_devs.h As you requested, this patch updates the documented procedure for submitting new unusual_devs.h entries. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/unusual_devs.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h index 69554115a098..6a25b58af696 100644 --- a/drivers/usb/storage/unusual_devs.h +++ b/drivers/usb/storage/unusual_devs.h @@ -36,13 +36,16 @@ /* If you edit this file, please try to keep it sorted first by VendorID, * then by ProductID. * - * If you want to add an entry for this file, please send the following - * to greg@kroah.com: - * - patch that adds the entry for your device which includes your - * email address right above the entry. + * If you want to add an entry for this file, be sure to include the + * following information: + * - a patch that adds the entry for your device, including your + * email address right above the entry (plus maybe a brief + * explanation of the reason for the entry), * - a copy of /proc/bus/usb/devices with your device plugged in * running with this patch. - * + * Send your submission to either Phil Dibowitz or + * Alan Stern , and don't forget to CC: the + * USB development list . */ UNUSUAL_DEV( 0x03ee, 0x6901, 0x0000, 0x0100, -- cgit v1.2.3 From 691d62e8f67f0a5ffd582001a5e58cddf0f35aa2 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Wed, 8 Sep 2004 20:04:05 -0700 Subject: [PATCH] USB: fix up usblp usb_unlink_urb() warning On Thursday 09 September 2004 08:38, Greg KH wrote: > On Thu, Sep 09, 2004 at 01:07:05AM -0400, Gene Heskett wrote: > > Greetings; > > > > I just had to reboot back to -mm2 after playing with some printer configs > > in cups, although the test pages worked, so I'm not sure what this all > > about, from var log/messages: > > > > Sep 8 23:13:42 coyote cups: cupsd -HUP succeeded > > Sep 8 23:13:43 coyote kernel: usb_unlink_urb() is deprecated for > > synchronous unlinks. Use usb_kill_urb() Sep 8 23:13:43 coyote kernel: > > Badness in usb_unlink_urb at drivers/usb/core/urb.c:456 Sep 8 23:13:44 > > coyote kernel: [] dump_stack+0x1e/0x20 Sep 8 23:13:44 coyote > > kernel: [] usb_unlink_urb+0x85/0xa0 Sep 8 23:13:44 coyote > > kernel: [] usblp_unlink_urbs+0x17/0x40 Sep 8 23:13:44 coyote > > kernel: [] usblp_release+0x38/0x60 Sep 8 23:13:44 coyote > > kernel: [] __fput+0x12a/0x140 > > Sep 8 23:13:44 coyote kernel: [] filp_close+0x57/0x80 > > Sep 8 23:13:44 coyote kernel: [] sys_close+0x61/0x90 > > Sep 8 23:13:44 coyote kernel: [] sysenter_past_esp+0x52/0x71 Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/usblp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/class/usblp.c b/drivers/usb/class/usblp.c index dd6950e674f4..3174bf78fefc 100644 --- a/drivers/usb/class/usblp.c +++ b/drivers/usb/class/usblp.c @@ -410,9 +410,9 @@ static void usblp_cleanup (struct usblp *usblp) static void usblp_unlink_urbs(struct usblp *usblp) { - usb_unlink_urb(usblp->writeurb); + usb_kill_urb(usblp->writeurb); if (usblp->bidir) - usb_unlink_urb(usblp->readurb); + usb_kill_urb(usblp->readurb); } static int usblp_release(struct inode *inode, struct file *file) -- cgit v1.2.3 From 03ef9d05be029dee489ba4cd911de5ca4bf8ec95 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Wed, 8 Sep 2004 20:04:25 -0700 Subject: [PATCH] USB: Unusual_devs entry for Panasonic cameras Thanks to Tom Hughes for jogging my memory about this patch, which has been sitting here waiting for Greg's return. It adds an unusual_devs.h entry for the Panasonic DMC-LCx line of cameras, which incorrectly report the total number of blocks in response to READ CAPACITY rather than the highest available block number. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/unusual_devs.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h index 6a25b58af696..9d0d846e19e3 100644 --- a/drivers/usb/storage/unusual_devs.h +++ b/drivers/usb/storage/unusual_devs.h @@ -173,6 +173,16 @@ UNUSUAL_DEV( 0x04da, 0x0d05, 0x0000, 0x0000, "CD-R/RW Drive", US_SC_8070, US_PR_CB, NULL, 0), +/* Reported by Adriaan Penning + * Note that these cameras report "Medium not present" after + * ALLOW_MEDIUM_REMOVAL, so they also need to be marked + * NOT_LOCKABLE in the SCSI blacklist (and the vendor is MATSHITA). */ +UNUSUAL_DEV( 0x04da, 0x2372, 0x0000, 0x9999, + "Panasonic", + "DMC-LCx Camera", + US_SC_DEVICE, US_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY ), + /* Most of the following entries were developed with the help of * Shuttle/SCM directly. */ -- cgit v1.2.3 From d2e1a8d0dd754d1666f680863792aa2cc8d4611e Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 8 Sep 2004 21:29:39 -0700 Subject: USB: fix usb_unlink_urb() usage in pl2303 driver Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/pl2303.c | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c index 416ea72951f6..5be9c370313b 100644 --- a/drivers/usb/serial/pl2303.c +++ b/drivers/usb/serial/pl2303.c @@ -621,7 +621,6 @@ static void pl2303_close (struct usb_serial_port *port, struct file *filp) struct pl2303_private *priv = usb_get_serial_port_data(port); unsigned long flags; unsigned int c_cflag; - int result; int bps; long timeout; wait_queue_t wait; \ @@ -666,23 +665,9 @@ static void pl2303_close (struct usb_serial_port *port, struct file *filp) /* shutdown our urbs */ dbg("%s - shutting down urbs", __FUNCTION__); - result = usb_unlink_urb (port->write_urb); - if (result) - dbg("%s - usb_unlink_urb (write_urb)" - " failed with reason: %d", __FUNCTION__, - result); - - result = usb_unlink_urb (port->read_urb); - if (result) - dbg("%s - usb_unlink_urb (read_urb) " - "failed with reason: %d", __FUNCTION__, - result); - - result = usb_unlink_urb (port->interrupt_in_urb); - if (result) - dbg("%s - usb_unlink_urb (interrupt_in_urb)" - " failed with reason: %d", __FUNCTION__, - result); + usb_kill_urb(port->write_urb); + usb_kill_urb(port->read_urb); + usb_kill_urb(port->interrupt_in_urb); if (port->tty) { c_cflag = port->tty->termios->c_cflag; -- cgit v1.2.3 From 48f9435bca1023b7db4b67ce89169e5559b92ad6 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 8 Sep 2004 21:32:18 -0700 Subject: USB: fix usb_unlink_urb() usage in usb-serial core Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/usb-serial.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index fd6bb67c69c1..6b2aa1f6aa86 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -455,15 +455,15 @@ static void destroy_serial(struct kref *kref) if (!port) continue; if (port->read_urb) { - usb_unlink_urb(port->read_urb); + usb_kill_urb(port->read_urb); usb_free_urb(port->read_urb); } if (port->write_urb) { - usb_unlink_urb(port->write_urb); + usb_kill_urb(port->write_urb); usb_free_urb(port->write_urb); } if (port->interrupt_in_urb) { - usb_unlink_urb(port->interrupt_in_urb); + usb_kill_urb(port->interrupt_in_urb); usb_free_urb(port->interrupt_in_urb); } kfree(port->bulk_in_buffer); @@ -819,15 +819,15 @@ static void port_release(struct device *dev) dbg ("%s - %s", __FUNCTION__, dev->bus_id); if (port->read_urb) { - usb_unlink_urb(port->read_urb); + usb_kill_urb(port->read_urb); usb_free_urb(port->read_urb); } if (port->write_urb) { - usb_unlink_urb(port->write_urb); + usb_kill_urb(port->write_urb); usb_free_urb(port->write_urb); } if (port->interrupt_in_urb) { - usb_unlink_urb(port->interrupt_in_urb); + usb_kill_urb(port->interrupt_in_urb); usb_free_urb(port->interrupt_in_urb); } kfree(port->bulk_in_buffer); -- cgit v1.2.3 From 2e7970b7fc623cb948063de694b2d25a16445064 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 8 Sep 2004 21:34:02 -0700 Subject: USB: fix usb_unlink_urb() usage in belkin_sa driver Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/belkin_sa.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/serial/belkin_sa.c b/drivers/usb/serial/belkin_sa.c index bcf56011ec22..a44cb9d96113 100644 --- a/drivers/usb/serial/belkin_sa.c +++ b/drivers/usb/serial/belkin_sa.c @@ -228,7 +228,7 @@ static int belkin_sa_open (struct usb_serial_port *port, struct file *filp) port->interrupt_in_urb->dev = port->serial->dev; retval = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); if (retval) { - usb_unlink_urb(port->read_urb); + usb_kill_urb(port->read_urb); err(" usb_submit_urb(read int) failed"); } @@ -242,9 +242,9 @@ static void belkin_sa_close (struct usb_serial_port *port, struct file *filp) dbg("%s port %d", __FUNCTION__, port->number); /* shutdown our bulk reads and writes */ - usb_unlink_urb (port->write_urb); - usb_unlink_urb (port->read_urb); - usb_unlink_urb (port->interrupt_in_urb); + usb_kill_urb(port->write_urb); + usb_kill_urb(port->read_urb); + usb_kill_urb(port->interrupt_in_urb); } /* belkin_sa_close */ -- cgit v1.2.3 From c4c3ce8e38324dccb78b8380217690f8dee0796f Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 8 Sep 2004 21:34:35 -0700 Subject: USB: fix usb_unlink_urb() usage in cyberjack driver Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/cyberjack.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/cyberjack.c b/drivers/usb/serial/cyberjack.c index 33613b008557..6b5ec2af32ad 100644 --- a/drivers/usb/serial/cyberjack.c +++ b/drivers/usb/serial/cyberjack.c @@ -149,7 +149,7 @@ static void cyberjack_shutdown (struct usb_serial *serial) dbg("%s", __FUNCTION__); for (i=0; i < serial->num_ports; ++i) { - usb_unlink_urb (serial->port[i]->interrupt_in_urb); + usb_kill_urb(serial->port[i]->interrupt_in_urb); /* My special items, the standard routines free my urbs */ kfree(usb_get_serial_port_data(serial->port[i])); usb_set_serial_port_data(serial->port[i], NULL); @@ -189,8 +189,8 @@ static void cyberjack_close (struct usb_serial_port *port, struct file *filp) if (port->serial->dev) { /* shutdown any bulk reads that might be going on */ - usb_unlink_urb (port->write_urb); - usb_unlink_urb (port->read_urb); + usb_kill_urb(port->write_urb); + usb_kill_urb(port->read_urb); } } -- cgit v1.2.3 From f0afcd40164946b36dc7da0016ed38c142ab2229 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 8 Sep 2004 21:35:24 -0700 Subject: USB: fix usb_unlink_urb() usage in whiteheat driver Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/whiteheat.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/serial/whiteheat.c b/drivers/usb/serial/whiteheat.c index b27741bf5497..fc20c8324fe7 100644 --- a/drivers/usb/serial/whiteheat.c +++ b/drivers/usb/serial/whiteheat.c @@ -680,7 +680,7 @@ static void whiteheat_close(struct usb_serial_port *port, struct file * filp) list_for_each_safe(tmp, tmp2, &info->rx_urbs_submitted) { wrap = list_entry(tmp, struct whiteheat_urb_wrap, list); urb = wrap->urb; - usb_unlink_urb(urb); + usb_kill_urb(urb); list_del(tmp); list_add(tmp, &info->rx_urbs_free); } @@ -691,7 +691,7 @@ static void whiteheat_close(struct usb_serial_port *port, struct file * filp) list_for_each_safe(tmp, tmp2, &info->tx_urbs_submitted) { wrap = list_entry(tmp, struct whiteheat_urb_wrap, list); urb = wrap->urb; - usb_unlink_urb(urb); + usb_kill_urb(urb); list_del(tmp); list_add(tmp, &info->tx_urbs_free); } @@ -1344,7 +1344,7 @@ static void stop_command_port(struct usb_serial *serial) spin_lock_irqsave(&command_info->lock, flags); command_info->port_running--; if (!command_info->port_running) - usb_unlink_urb(command_port->read_urb); + usb_kill_urb(command_port->read_urb); spin_unlock_irqrestore(&command_info->lock, flags); } @@ -1372,7 +1372,7 @@ static int start_port_read(struct usb_serial_port *port) list_for_each_safe(tmp, tmp2, &info->rx_urbs_submitted) { wrap = list_entry(tmp, struct whiteheat_urb_wrap, list); urb = wrap->urb; - usb_unlink_urb(urb); + usb_kill_urb(urb); list_del(tmp); list_add(tmp, &info->rx_urbs_free); } -- cgit v1.2.3 From 0f694983eb9af685caf003f94e920db85326cf47 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 8 Sep 2004 21:36:08 -0700 Subject: USB: fix usb_unlink_urb() usage in io_edgeport driver Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/io_edgeport.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/io_edgeport.c b/drivers/usb/serial/io_edgeport.c index fc3a536f3696..d8d492ef99e8 100644 --- a/drivers/usb/serial/io_edgeport.c +++ b/drivers/usb/serial/io_edgeport.c @@ -1243,7 +1243,7 @@ static void edge_close (struct usb_serial_port *port, struct file * filp) edge_port->openPending = FALSE; if (edge_port->write_urb) { - usb_unlink_urb (edge_port->write_urb); + usb_kill_urb(edge_port->write_urb); } if (edge_port->write_urb) { @@ -2448,8 +2448,8 @@ static int write_cmd_usb (struct edgeport_port *edge_port, unsigned char *buffer if (status) { /* something went wrong */ dbg("%s - usb_submit_urb(write bulk) failed", __FUNCTION__); - usb_unlink_urb (urb); - usb_free_urb (urb); + usb_kill_urb(urb); + usb_free_urb(urb); return status; } -- cgit v1.2.3 From 221b7993fc1bbafe3bd404c2135728a496e6a6a8 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 8 Sep 2004 21:41:32 -0700 Subject: USB: fix usb_unlink_urb() usage in ir-usb driver Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/ir-usb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/serial/ir-usb.c b/drivers/usb/serial/ir-usb.c index cb42cc7c3224..f3a75de21d66 100644 --- a/drivers/usb/serial/ir-usb.c +++ b/drivers/usb/serial/ir-usb.c @@ -322,7 +322,7 @@ static void ir_close (struct usb_serial_port *port, struct file * filp) dbg("%s - port %d", __FUNCTION__, port->number); /* shutdown our bulk read */ - usb_unlink_urb (port->read_urb); + usb_kill_urb(port->read_urb); } static int ir_write (struct usb_serial_port *port, int from_user, const unsigned char *buf, int count) -- cgit v1.2.3 From 7abd81fca985aa5bd7c90c44775005ab47e8cbe9 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 8 Sep 2004 21:42:45 -0700 Subject: USB: fix usb_unlink_urb() usage in ipaq driver Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/ipaq.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/serial/ipaq.c b/drivers/usb/serial/ipaq.c index 00cb88dd67b6..1a748b515112 100644 --- a/drivers/usb/serial/ipaq.c +++ b/drivers/usb/serial/ipaq.c @@ -288,8 +288,8 @@ static void ipaq_close(struct usb_serial_port *port, struct file *filp) /* * shut down bulk read and write */ - usb_unlink_urb(port->write_urb); - usb_unlink_urb(port->read_urb); + usb_kill_urb(port->write_urb); + usb_kill_urb(port->read_urb); ipaq_destroy_lists(port); kfree(priv); usb_set_serial_port_data(port, NULL); -- cgit v1.2.3 From 96f155845c12d7f8aee9b6e34dd1e94f11c0ea4f Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 8 Sep 2004 21:44:04 -0700 Subject: USB: fix usb_unlink_urb() usage in digi_acceleport driver Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/digi_acceleport.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index 6d01337eefc0..5c4669db9b86 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -1623,7 +1623,7 @@ dbg( "digi_close: TOP: port=%d, open_count=%d", priv->dp_port_num, port->open_co DIGI_CLOSE_TIMEOUT ); /* shutdown any outstanding bulk writes */ - usb_unlink_urb (port->write_urb); + usb_kill_urb(port->write_urb); } tty->closing = 0; @@ -1762,8 +1762,8 @@ dbg( "digi_shutdown: TOP, in_interrupt()=%ld", in_interrupt() ); /* stop reads and writes on all ports */ for( i=0; itype->num_ports+1; i++ ) { - usb_unlink_urb( serial->port[i]->read_urb ); - usb_unlink_urb( serial->port[i]->write_urb ); + usb_kill_urb(serial->port[i]->read_urb); + usb_kill_urb(serial->port[i]->write_urb); } /* free the private data structures for all ports */ -- cgit v1.2.3 From 9cc3cf0a6eb86359f330ea63e8c45219beb7cacc Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 8 Sep 2004 21:45:53 -0700 Subject: USB: fix usb_unlink_urb() usage in empeg driver Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/empeg.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/serial/empeg.c b/drivers/usb/serial/empeg.c index 972f3b91d41c..edb3559d546a 100644 --- a/drivers/usb/serial/empeg.c +++ b/drivers/usb/serial/empeg.c @@ -185,7 +185,7 @@ static void empeg_close (struct usb_serial_port *port, struct file * filp) dbg("%s - port %d", __FUNCTION__, port->number); /* shutdown our bulk read */ - usb_unlink_urb (port->read_urb); + usb_kill_urb(port->read_urb); /* Uncomment the following line if you want to see some statistics in your syslog */ /* dev_info (&port->dev, "Bytes In = %d Bytes Out = %d\n", bytes_in, bytes_out); */ } @@ -406,7 +406,7 @@ static void empeg_read_bulk_callback (struct urb *urb, struct pt_regs *regs) static void empeg_throttle (struct usb_serial_port *port) { dbg("%s - port %d", __FUNCTION__, port->number); - usb_unlink_urb (port->read_urb); + usb_kill_urb(port->read_urb); } @@ -583,10 +583,10 @@ static void __exit empeg_exit (void) for (i = 0; i < NUM_URBS; ++i) { if (write_urb_pool[i]) { - /* FIXME - uncomment the following usb_unlink_urb call when + /* FIXME - uncomment the following usb_kill_urb call when * the host controllers get fixed to set urb->dev = NULL after * the urb is finished. Otherwise this call oopses. */ - /* usb_unlink_urb(write_urb_pool[i]); */ + /* usb_kill_urb(write_urb_pool[i]); */ if (write_urb_pool[i]->transfer_buffer) kfree(write_urb_pool[i]->transfer_buffer); usb_free_urb (write_urb_pool[i]); -- cgit v1.2.3 From 6af886e661ff4460e4e612ca1bff5e8d670cee34 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 8 Sep 2004 21:47:46 -0700 Subject: USB: fix usb_unlink_urb() usage in mct_u232 driver Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/mct_u232.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/mct_u232.c b/drivers/usb/serial/mct_u232.c index b4961f0d2509..3ae89d03b883 100644 --- a/drivers/usb/serial/mct_u232.c +++ b/drivers/usb/serial/mct_u232.c @@ -480,9 +480,9 @@ static void mct_u232_close (struct usb_serial_port *port, struct file *filp) if (port->serial->dev) { /* shutdown our urbs */ - usb_unlink_urb (port->write_urb); - usb_unlink_urb (port->read_urb); - usb_unlink_urb (port->interrupt_in_urb); + usb_kill_urb(port->write_urb); + usb_kill_urb(port->read_urb); + usb_kill_urb(port->interrupt_in_urb); } } /* mct_u232_close */ -- cgit v1.2.3 From d3d55268a92ebe297934969db00762a4c3ab1e70 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 8 Sep 2004 21:48:32 -0700 Subject: USB: fix usb_unlink_urb() usage in omninet driver Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/omninet.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/serial/omninet.c b/drivers/usb/serial/omninet.c index 371aa2e8e3f1..7032d5f7c6e1 100644 --- a/drivers/usb/serial/omninet.c +++ b/drivers/usb/serial/omninet.c @@ -183,8 +183,8 @@ static void omninet_close (struct usb_serial_port *port, struct file * filp) dbg("%s - port %d", __FUNCTION__, port->number); wport = serial->port[1]; - usb_unlink_urb(wport->write_urb); - usb_unlink_urb(port->read_urb); + usb_kill_urb(wport->write_urb); + usb_kill_urb(port->read_urb); od = usb_get_serial_port_data(port); if (od) -- cgit v1.2.3 From 2af16876c4e7dac7787163a19b6b44dbd7ce4941 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 8 Sep 2004 21:49:19 -0700 Subject: USB: fix usb_unlink_urb() usage in visor driver Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/visor.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/visor.c b/drivers/usb/serial/visor.c index 8a0a75533e70..0e5d90552789 100644 --- a/drivers/usb/serial/visor.c +++ b/drivers/usb/serial/visor.c @@ -446,9 +446,9 @@ static void visor_close (struct usb_serial_port *port, struct file * filp) dbg("%s - port %d", __FUNCTION__, port->number); /* shutdown our urbs */ - usb_unlink_urb (port->read_urb); + usb_kill_urb(port->read_urb); if (port->interrupt_in_urb) - usb_unlink_urb (port->interrupt_in_urb); + usb_kill_urb(port->interrupt_in_urb); /* Try to send shutdown message, if the device is gone, this will just fail. */ transfer_buffer = kmalloc (0x12, GFP_KERNEL); @@ -655,7 +655,7 @@ exit: static void visor_throttle (struct usb_serial_port *port) { dbg("%s - port %d", __FUNCTION__, port->number); - usb_unlink_urb (port->read_urb); + usb_kill_urb(port->read_urb); } -- cgit v1.2.3 From 6b8f5df3fd50592f962b5ffeda8b197fdc6e47fa Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 8 Sep 2004 21:49:56 -0700 Subject: USB: fix usb_unlink_urb() usage in kl5kusb105 driver Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/kl5kusb105.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/usb/serial/kl5kusb105.c b/drivers/usb/serial/kl5kusb105.c index c5207d19401a..9e60b3476775 100644 --- a/drivers/usb/serial/kl5kusb105.c +++ b/drivers/usb/serial/kl5kusb105.c @@ -336,12 +336,12 @@ static void klsi_105_shutdown (struct usb_serial *serial) for (j = 0; j < NUM_URBS; j++) { if (write_urbs[j]) { /* FIXME - uncomment the following - * usb_unlink_urb call when the host + * usb_kill_urb call when the host * controllers get fixed to set * urb->dev = NULL after the urb is * finished. Otherwise this call * oopses. */ - /* usb_unlink_urb(write_urbs[j]); */ + /* usb_kill_urb(write_urbs[j]); */ if (write_urbs[j]->transfer_buffer) kfree(write_urbs[j]->transfer_buffer); usb_free_urb (write_urbs[j]); @@ -467,12 +467,12 @@ static void klsi_105_close (struct usb_serial_port *port, struct file *filp) err("Disabling read failed (error = %d)", rc); /* shutdown our bulk reads and writes */ - usb_unlink_urb (port->write_urb); - usb_unlink_urb (port->read_urb); + usb_kill_urb(port->write_urb); + usb_kill_urb(port->read_urb); /* unlink our write pool */ /* FIXME */ /* wgg - do I need this? I think so. */ - usb_unlink_urb (port->interrupt_in_urb); + usb_kill_urb(port->interrupt_in_urb); info("kl5kusb105 port stats: %ld bytes in, %ld bytes out", priv->bytes_in, priv->bytes_out); } /* klsi_105_close */ @@ -994,7 +994,7 @@ static int klsi_105_ioctl (struct usb_serial_port *port, struct file * file, static void klsi_105_throttle (struct usb_serial_port *port) { dbg("%s - port %d", __FUNCTION__, port->number); - usb_unlink_urb (port->read_urb); + usb_kill_urb(port->read_urb); } static void klsi_105_unthrottle (struct usb_serial_port *port) -- cgit v1.2.3 From f3a87694aea417c9d61b99c41025cbcc21c16727 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 8 Sep 2004 21:50:55 -0700 Subject: USB: fix usb_unlink_urb() usage in kobil_sct driver Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/kobil_sct.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/drivers/usb/serial/kobil_sct.c b/drivers/usb/serial/kobil_sct.c index b479916ce269..33539874bd0c 100644 --- a/drivers/usb/serial/kobil_sct.c +++ b/drivers/usb/serial/kobil_sct.c @@ -350,14 +350,13 @@ static void kobil_close (struct usb_serial_port *port, struct file *filp) { dbg("%s - port %d", __FUNCTION__, port->number); - if (port->write_urb){ - usb_unlink_urb( port->write_urb ); + if (port->write_urb) { + usb_kill_urb(port->write_urb); usb_free_urb( port->write_urb ); port->write_urb = NULL; } - if (port->interrupt_in_urb){ - usb_unlink_urb (port->interrupt_in_urb); - } + if (port->interrupt_in_urb) + usb_kill_urb(port->interrupt_in_urb); } @@ -458,9 +457,8 @@ static int kobil_write (struct usb_serial_port *port, int from_user, ((priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID) && (priv->filled > 3) && (priv->filled >= (priv->buf[2] + 4))) ) { // stop reading (except TWIN and KAAN SIM) - if ( (priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID) || (priv->device_type == KOBIL_ADAPTER_K_PRODUCT_ID) ) { - usb_unlink_urb( port->interrupt_in_urb ); - } + if ( (priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID) || (priv->device_type == KOBIL_ADAPTER_K_PRODUCT_ID) ) + usb_kill_urb(port->interrupt_in_urb); todo = priv->filled - priv->cur_pos; -- cgit v1.2.3 From 1f859b6efd97a06fc8bc5476f895438da16e4b1c Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 8 Sep 2004 21:51:42 -0700 Subject: USB: fix usb_unlink_urb() usage in io_ti driver Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/io_ti.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/io_ti.c b/drivers/usb/serial/io_ti.c index 55be299fdf84..41afb4c77f69 100644 --- a/drivers/usb/serial/io_ti.c +++ b/drivers/usb/serial/io_ti.c @@ -1977,7 +1977,7 @@ static void edge_close (struct usb_serial_port *port, struct file * filp) /* chase the port close */ TIChasePort (edge_port); - usb_unlink_urb (port->read_urb); + usb_kill_urb(port->read_urb); /* assuming we can still talk to the device, * send a close port command to it */ @@ -1992,7 +1992,7 @@ static void edge_close (struct usb_serial_port *port, struct file * filp) --edge_port->edge_serial->num_ports_open; if (edge_port->edge_serial->num_ports_open <= 0) { /* last port is now closed, let's shut down our interrupt urb */ - usb_unlink_urb (port->serial->port[0]->interrupt_in_urb); + usb_kill_urb(port->serial->port[0]->interrupt_in_urb); edge_port->edge_serial->num_ports_open = 0; } edge_port->close_pending = 0; @@ -2126,7 +2126,7 @@ static void edge_throttle (struct usb_serial_port *port) status = TIClearRts (edge_port); } - usb_unlink_urb (port->read_urb); + usb_kill_urb(port->read_urb); } static void edge_unthrottle (struct usb_serial_port *port) -- cgit v1.2.3 From 8f5d1a12813f8a7c4a42c753e5ada18fc1e04b63 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 8 Sep 2004 21:52:22 -0700 Subject: USB: fix usb_unlink_urb() usage in ftdi_sio driver Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/ftdi_sio.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index b72f7ad4d05e..9d3a8e831326 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -1488,16 +1488,8 @@ static void ftdi_close (struct usb_serial_port *port, struct file *filp) } /* Note change no line if hupcl is off */ /* shutdown our bulk read */ - if (port->read_urb) { - if (usb_unlink_urb (port->read_urb) < 0) { - /* Generally, this isn't an error. If the previous - read bulk callback occurred (or is about to occur) - while the port was being closed or was throtted - (and is still throttled), the read urb will not - have been submitted. */ - dbg("%s - failed to unlink read urb (generally not an error)", __FUNCTION__); - } - } + if (port->read_urb) + usb_kill_urb(port->read_urb); } /* ftdi_close */ -- cgit v1.2.3 From 5426b1c448a21a259f545827f049f5edb23cb9d5 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 8 Sep 2004 21:54:43 -0700 Subject: USB: fix usb_unlink_urb() usage in keyspan_pda driver Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/keyspan_pda.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index fd6c1b8388aa..47c60ea09e9f 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -291,7 +291,7 @@ static void keyspan_pda_rx_throttle (struct usb_serial_port *port) upon the device too. */ dbg("keyspan_pda_rx_throttle port %d", port->number); - usb_unlink_urb(port->interrupt_in_urb); + usb_kill_urb(port->interrupt_in_urb); } @@ -712,8 +712,8 @@ static void keyspan_pda_close(struct usb_serial_port *port, struct file *filp) keyspan_pda_set_modem_info(serial, 0); /* shutdown our bulk reads and writes */ - usb_unlink_urb (port->write_urb); - usb_unlink_urb (port->interrupt_in_urb); + usb_kill_urb(port->write_urb); + usb_kill_urb(port->interrupt_in_urb); } } -- cgit v1.2.3 From 6cd8c6c6351b9cdf210bea6816925018eac7d700 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 8 Sep 2004 21:56:19 -0700 Subject: USB: fix usb_unlink_urb() usage in generic usb-serial driver Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/generic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c index 196fea084f21..c88885f8ca05 100644 --- a/drivers/usb/serial/generic.c +++ b/drivers/usb/serial/generic.c @@ -147,9 +147,9 @@ static void generic_cleanup (struct usb_serial_port *port) if (serial->dev) { /* shutdown any bulk reads that might be going on */ if (serial->num_bulk_out) - usb_unlink_urb (port->write_urb); + usb_kill_urb(port->write_urb); if (serial->num_bulk_in) - usb_unlink_urb (port->read_urb); + usb_kill_urb(port->read_urb); } } -- cgit v1.2.3 From c1a4dd61d48e857e1583ceb4b6fa707fbedeb96f Mon Sep 17 00:00:00 2001 From: David Brownell Date: Thu, 9 Sep 2004 02:05:09 -0700 Subject: [PATCH] export usb_set_device_state(), use in ohci This patch is mostly cleanup, but it all helps make PM_SUSPEND_DISK start to behave better. This exports the new usb_set_device_state() routine for the virtual root hubs, and uses it in OHCI during resume after power-off to replace some HC-private code doing almost the same thing. Note that all HCDs will likely need the same kind of suspend-to-disk support (though it's different when BIOS kicks in). Some systems even power-off during suspend-to-ram (to save extra power), which is why OHCI already has this logic! Related updates: - Use usb_set_device_state() immediately when an HC dies, making khubd handle disconnect processing instead of a workqueue. So now drivers won't self-deadlock in this should-be-rare path, when disconnect() calls flush_scheduled_work(). - Don't warn about "Unlink after no-IRQ" for the the root hub's status URB ... like when suspending an HCD that never enumerated a device. - Minor IRQ handler cleanup, including more accurate tracking of whether this driver ever returned IRQ_HANDLED (shared IRQs don't count). Signed-off-by: David Brownell Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hcd.c | 29 ++++++----------------------- drivers/usb/core/hcd.h | 4 +++- drivers/usb/core/hub.c | 3 ++- drivers/usb/core/usb.h | 2 -- drivers/usb/host/ohci-hcd.c | 14 +------------- 5 files changed, 12 insertions(+), 40 deletions(-) diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 08a3378bc875..c1de465a10d1 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -1263,7 +1263,7 @@ static int hcd_unlink_urb (struct urb *urb, int status) * never get completion IRQs ... maybe even the ones we need to * finish unlinking the initial failed usb_set_address(). */ - if (!hcd->saw_irq) { + if (!hcd->saw_irq && hcd->rh_timer.data != (unsigned long) urb) { dev_warn (hcd->self.controller, "Unlink after no-IRQ? " "Different ACPI or APIC settings may help." "\n"); @@ -1572,13 +1572,12 @@ irqreturn_t usb_hcd_irq (int irq, void *__hcd, struct pt_regs * r) struct usb_hcd *hcd = __hcd; int start = hcd->state; - if (unlikely (hcd->state == USB_STATE_HALT)) /* irq sharing? */ + if (start == USB_STATE_HALT) return IRQ_NONE; - - hcd->saw_irq = 1; if (hcd->driver->irq (hcd, r) == IRQ_NONE) return IRQ_NONE; + hcd->saw_irq = 1; if (hcd->state != start && hcd->state == USB_STATE_HALT) usb_hc_died (hcd); return IRQ_HANDLED; @@ -1587,22 +1586,6 @@ EXPORT_SYMBOL (usb_hcd_irq); /*-------------------------------------------------------------------------*/ -static void hcd_panic (void *_hcd) -{ - struct usb_hcd *hcd = _hcd; - struct usb_device *hub = hcd->self.root_hub; - unsigned i; - - /* hc's root hub is removed later removed in hcd->stop() */ - down (&hub->serialize); - usb_set_device_state(hub, USB_STATE_NOTATTACHED); - for (i = 0; i < hub->maxchild; i++) { - if (hub->children [i]) - usb_disconnect (&hub->children [i]); - } - up (&hub->serialize); -} - /** * usb_hc_died - report abnormal shutdown of a host controller (bus glue) * @hcd: pointer to the HCD representing the controller @@ -1615,9 +1598,9 @@ void usb_hc_died (struct usb_hcd *hcd) { dev_err (hcd->self.controller, "HC died; cleaning up\n"); - /* clean up old urbs and devices; needs a task context */ - INIT_WORK (&hcd->work, hcd_panic, hcd); - (void) schedule_work (&hcd->work); + /* make khubd clean up old urbs and devices */ + usb_set_device_state(hcd->self.root_hub, USB_STATE_NOTATTACHED); + mod_timer(&hcd->rh_timer, jiffies); } EXPORT_SYMBOL (usb_hc_died); diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h index 90e7d081b484..5e0337dfbcc9 100644 --- a/drivers/usb/core/hcd.h +++ b/drivers/usb/core/hcd.h @@ -67,7 +67,6 @@ struct usb_hcd { /* usb_bus.hcpriv points to this */ struct timer_list rh_timer; /* drives root hub */ struct list_head dev_list; /* devices on this bus */ - struct work_struct work; /* * hardware info/state @@ -363,6 +362,9 @@ static inline int hcd_register_root (struct usb_device *usb_dev, return usb_register_root_hub (usb_dev, hcd->self.controller); } +extern void usb_set_device_state(struct usb_device *udev, + enum usb_device_state new_state); + /*-------------------------------------------------------------------------*/ /* exported only within usbcore */ diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 8c9f120ea9aa..499ce8a1a388 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -910,7 +910,7 @@ static int locktree(struct usb_device *udev) } /** - * usb_set_device_state - change a device's current state (usbcore-internal) + * usb_set_device_state - change a device's current state (usbcore, hcds) * @udev: pointer to device whose state should be changed * @new_state: new state value to be stored * @@ -941,6 +941,7 @@ void usb_set_device_state(struct usb_device *udev, recursively_mark_NOTATTACHED(udev); spin_unlock_irqrestore(&device_state_lock, flags); } +EXPORT_SYMBOL(usb_set_device_state); static void choose_address(struct usb_device *udev) diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index b44bb8d7cdfa..8e9c923bff0b 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -22,8 +22,6 @@ extern int usb_get_device_descriptor(struct usb_device *dev, unsigned int size); extern int usb_set_configuration(struct usb_device *dev, int configuration); -extern void usb_set_device_state(struct usb_device *udev, - enum usb_device_state new_state); /* for labeling diagnostics */ extern const char *usbcore_name; diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index 12cc2844906d..10486ee608d2 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -726,18 +726,6 @@ static void ohci_stop (struct usb_hcd *hcd) #if defined(CONFIG_USB_SUSPEND) || defined(CONFIG_PM) -static void mark_children_gone (struct usb_device *dev) -{ - unsigned i; - - for (i = 0; i < dev->maxchild; i++) { - if (dev->children [i] == 0) - continue; - dev->children [i]->state = USB_STATE_NOTATTACHED; - mark_children_gone (dev->children [i]); - } -} - static int hc_restart (struct ohci_hcd *ohci) { int temp; @@ -751,7 +739,7 @@ static int hc_restart (struct ohci_hcd *ohci) */ spin_lock_irq(&ohci->lock); disable (ohci); - mark_children_gone (ohci->hcd.self.root_hub); + usb_set_device_state (ohci->hcd.self.root_hub, USB_STATE_NOTATTACHED); if (!list_empty (&ohci->pending)) ohci_dbg(ohci, "abort schedule...\n"); list_for_each_entry (priv, &ohci->pending, pending) { -- cgit v1.2.3 From a16d62137975ce06f514406c00eff823536ffd1a Mon Sep 17 00:00:00 2001 From: David Brownell Date: Thu, 9 Sep 2004 02:05:48 -0700 Subject: [PATCH] USB: gadget_is_n9604 This adds recognition of one more UDC driver. (Driver is available separately.) Tell gadget drivers about the National 9603/9604 UDC. Signed-off-by: David Brownell Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/ether.c | 6 ++++++ drivers/usb/gadget/file_storage.c | 4 +++- drivers/usb/gadget/gadget_chips.h | 6 ++++++ drivers/usb/gadget/zero.c | 2 ++ 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/usb/gadget/ether.c b/drivers/usb/gadget/ether.c index 763d0552146a..0c5f37c2ba0a 100644 --- a/drivers/usb/gadget/ether.c +++ b/drivers/usb/gadget/ether.c @@ -231,6 +231,10 @@ MODULE_PARM_DESC(host_addr, "Host Ethernet Address"); #define DEV_CONFIG_CDC #endif +#ifdef CONFIG_USB_GADGET_N9604 +#define DEV_CONFIG_CDC +#endif + /* For CDC-incapable hardware, choose the simple cdc subset. * Anything that talks bulk (without notable bugs) can do this. @@ -2334,6 +2338,8 @@ eth_bind (struct usb_gadget *gadget) device_desc.bcdDevice = __constant_cpu_to_le16 (0x0208); } else if (gadget_is_lh7a40x(gadget)) { device_desc.bcdDevice = __constant_cpu_to_le16 (0x0209); + } else if (gadget_is_n9604(gadget)) { + device_desc.bcdDevice = __constant_cpu_to_le16 (0x020a); } else { /* can't assume CDC works. don't want to default to * anything less functional on CDC-capable hardware, diff --git a/drivers/usb/gadget/file_storage.c b/drivers/usb/gadget/file_storage.c index 1280a7cfacf4..2d2b2c032f19 100644 --- a/drivers/usb/gadget/file_storage.c +++ b/drivers/usb/gadget/file_storage.c @@ -3728,8 +3728,10 @@ static int __init check_parameters(struct fsg_dev *fsg) mod_data.release = __constant_cpu_to_le16(0x0307); else if (gadget_is_omap(fsg->gadget)) mod_data.release = __constant_cpu_to_le16(0x0308); - else if (gadget_is_lh7a40x(gadget)) + else if (gadget_is_lh7a40x(fsg->gadget)) mod_data.release = __constant_cpu_to_le16 (0x0309); + else if (gadget_is_n9604(fsg->gadget)) + mod_data.release = __constant_cpu_to_le16 (0x030a); else { WARN(fsg, "controller '%s' not recognized\n", fsg->gadget->name); diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h index e24e2f9ad28e..f6273701fec6 100644 --- a/drivers/usb/gadget/gadget_chips.h +++ b/drivers/usb/gadget/gadget_chips.h @@ -62,6 +62,12 @@ #define gadget_is_omap(g) 0 #endif +#ifdef CONFIG_USB_GADGET_N9604 +#define gadget_is_n9604(g) !strcmp("n9604_udc", (g)->name) +#else +#define gadget_is_n9604(g) 0 +#endif + // CONFIG_USB_GADGET_AT91RM9200 // CONFIG_USB_GADGET_SX2 // CONFIG_USB_GADGET_AU1X00 diff --git a/drivers/usb/gadget/zero.c b/drivers/usb/gadget/zero.c index a415a33ed117..69962c30a3a9 100644 --- a/drivers/usb/gadget/zero.c +++ b/drivers/usb/gadget/zero.c @@ -1188,6 +1188,8 @@ autoconf_fail: device_desc.bcdDevice = __constant_cpu_to_le16 (0x0208); } else if (gadget_is_lh7a40x(gadget)) { device_desc.bcdDevice = __constant_cpu_to_le16 (0x0209); + } else if (gadget_is_n9604(gadget)) { + device_desc.bcdDevice = __constant_cpu_to_le16 (0x020a); } else { /* gadget zero is so simple (for now, no altsettings) that * it SHOULD NOT have problems with bulk-capable hardware. -- cgit v1.2.3 From 65a7d3e495b3a02d58e28cb61eecf5a212509031 Mon Sep 17 00:00:00 2001 From: David Brownell Date: Thu, 9 Sep 2004 02:06:55 -0700 Subject: [PATCH] USB: ohci updates Small bugfixes, at least one of which gets rid of some rather random behavior from certain board init behaviors. OHCI updates: - Bugfix the code taking frame clock adjustments from the boot loader. A recent change had a bug which caused inconsistent failures on some OHCI configs, including amd756. Thanks to for tracking down the specifics. - From Lothar Wassmann two fixes: (a) don't let tick clock sign-extend, that can make unlinks take excessively long (could happen easily enough); (b) when re-activating schedules after suspend, use the right bitmask (rare/exotic) - When suspending the root hub, mark it as USB_STATE_SUSPENDED Signed-off-by: David Brownell Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-dbg.c | 4 ++-- drivers/usb/host/ohci-hcd.c | 16 ++++++++++++---- drivers/usb/host/ohci-hub.c | 9 +++++---- drivers/usb/host/ohci.h | 5 +++-- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/drivers/usb/host/ohci-dbg.c b/drivers/usb/host/ohci-dbg.c index 54daaacdc8c9..532164079cfa 100644 --- a/drivers/usb/host/ohci-dbg.c +++ b/drivers/usb/host/ohci-dbg.c @@ -640,14 +640,14 @@ show_registers (struct class_device *class_dev, char *buf) rdata = ohci_readl (®s->fminterval); temp = scnprintf (next, size, "fmintvl 0x%08x %sFSMPS=0x%04x FI=0x%04x\n", - rdata, (rdata >> 31) ? " FIT" : "", + rdata, (rdata >> 31) ? "FIT " : "", (rdata >> 16) & 0xefff, rdata & 0xffff); size -= temp; next += temp; rdata = ohci_readl (®s->fmremaining); temp = scnprintf (next, size, "fmremaining 0x%08x %sFR=0x%04x\n", - rdata, (rdata >> 31) ? " FRT" : "", + rdata, (rdata >> 31) ? "FRT " : "", rdata & 0x3fff); size -= temp; next += temp; diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index 10486ee608d2..d96a4edbd022 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -409,11 +409,19 @@ static int hc_reset (struct ohci_hcd *ohci) /* boot firmware should have set this up (5.1.1.3.1) */ if (!ohci->fminterval) { + u32 t2; + temp = ohci_readl (&ohci->regs->fminterval); - if (temp & 0x3fff0000) - ohci->fminterval = temp; - else - ohci->fminterval = DEFAULT_FMINTERVAL; + ohci->fminterval = temp & 0x3fff; + if (ohci->fminterval != FI) + ohci_dbg (ohci, "fminterval delta %d\n", + ohci->fminterval - FI); + + t2 = FSMP (ohci->fminterval); + temp >>= 16; + if ((t2/2) < temp || temp > t2) + temp = t2; + ohci->fminterval |= temp << 16; /* also: power/overcurrent flags in roothub.a */ } diff --git a/drivers/usb/host/ohci-hub.c b/drivers/usb/host/ohci-hub.c index 344b1cab16e1..11093ed39cb1 100644 --- a/drivers/usb/host/ohci-hub.c +++ b/drivers/usb/host/ohci-hub.c @@ -146,10 +146,11 @@ static int ohci_hub_suspend (struct usb_hcd *hcd) ohci->next_statechange = jiffies + msecs_to_jiffies (5); succeed: - /* it's not USB_STATE_SUSPENDED unless access to this + /* it's not HCD_STATE_SUSPENDED unless access to this * hub from the non-usb side (PCI, SOC, etc) stopped */ root->dev.power.power_state = 3; + root->state = USB_STATE_SUSPENDED; done: spin_unlock_irq (&ohci->lock); return status; @@ -289,7 +290,7 @@ static int ohci_hub_resume (struct usb_hcd *hcd) ohci->hc_control |= enables; writel (ohci->hc_control, &ohci->regs->control); if (temp) - writel (status, &ohci->regs->cmdstatus); + writel (temp, &ohci->regs->cmdstatus); (void) ohci_readl (&ohci->regs->control); } @@ -481,8 +482,8 @@ static void start_hnp(struct ohci_hcd *ohci); /* this timer value might be vendor-specific ... */ #define PORT_RESET_HW_MSEC 10 -/* wrap-aware logic stolen from */ -#define tick_before(t1,t2) ((((s16)(t1))-((s16)(t2))) < 0) +/* wrap-aware logic morphed from */ +#define tick_before(t1,t2) ((s16)(((s16)(t1))-((s16)(t2))) < 0) /* called from some task, normally khubd */ static inline void root_port_reset (struct ohci_hcd *ohci, unsigned port) diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h index c60f445d0011..079bbc1b00c9 100644 --- a/drivers/usb/host/ohci.h +++ b/drivers/usb/host/ohci.h @@ -406,13 +406,14 @@ static inline void disable (struct ohci_hcd *ohci) } #define FI 0x2edf /* 12000 bits per frame (-1) */ -#define DEFAULT_FMINTERVAL ((((6 * (FI - 210)) / 7) << 16) | FI) +#define FSMP(fi) ((6 * ((fi) - 210)) / 7) #define LSTHRESH 0x628 /* lowspeed bit threshold */ static inline void periodic_reinit (struct ohci_hcd *ohci) { + u32 fi = ohci->fminterval & 0x0ffff; writel (ohci->fminterval, &ohci->regs->fminterval); - writel (((9 * FI) / 10) & 0x3fff, &ohci->regs->periodicstart); + writel (((9 * fi) / 10) & 0x3fff, &ohci->regs->periodicstart); writel (LSTHRESH, &ohci->regs->lsthresh); } -- cgit v1.2.3 From 697910158a242b972329b1f99037ae814766f8ba Mon Sep 17 00:00:00 2001 From: David Brownell Date: Thu, 9 Sep 2004 02:07:40 -0700 Subject: [PATCH] USB: khubd looks at ports after probe Fix the hub probe logic so that when khubd wakes up, it will actually look at all ports on the new hub. This resolved a root hub enumeration issue, and I expect the "boot from USB disk" folk will like this too. The fix includes cleanup: centralizing the logic to make khubd look at a given hub, instead of cloning it three times (or in the case of hub probing, frankensteining it). This also adds a FIXME to the new routine centralizing disconnect processing: we also want "power down port but don't wake khubd" (for PM_SUSPEND_DISK as well as SRP). Signed-off-by: David Brownell Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 47 +++++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 499ce8a1a388..13879305bf01 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -39,12 +39,13 @@ /* Protect struct usb_device state and children members */ static spinlock_t device_state_lock = SPIN_LOCK_UNLOCKED; -/* Wakes up khubd */ +/* khubd's worklist and its lock */ static spinlock_t hub_event_lock = SPIN_LOCK_UNLOCKED; - static LIST_HEAD(hub_event_list); /* List of hubs needing servicing */ +/* Wakes up khubd */ static DECLARE_WAIT_QUEUE_HEAD(khubd_wait); + static pid_t khubd_pid = 0; /* PID of khubd */ static DECLARE_COMPLETION(khubd_exited); @@ -226,6 +227,19 @@ static int get_port_status(struct usb_device *hdev, int port, data, sizeof(*data), HZ * USB_CTRL_GET_TIMEOUT); } +static void kick_khubd(struct usb_hub *hub) +{ + unsigned long flags; + + spin_lock_irqsave(&hub_event_lock, flags); + if (list_empty(&hub->event_list)) { + list_add_tail(&hub->event_list, &hub_event_list); + wake_up(&khubd_wait); + } + spin_unlock_irqrestore(&hub_event_lock, flags); +} + + /* completion function, fires on port status changes and various faults */ static void hub_irq(struct urb *urb, struct pt_regs *regs) { @@ -261,12 +275,7 @@ static void hub_irq(struct urb *urb, struct pt_regs *regs) hub->nerrors = 0; /* Something happened, let khubd figure it out */ - spin_lock(&hub_event_lock); - if (list_empty(&hub->event_list)) { - list_add_tail(&hub->event_list, &hub_event_list); - wake_up(&khubd_wait); - } - spin_unlock(&hub_event_lock); + kick_khubd(hub); resubmit: if (hub->quiescing) @@ -579,6 +588,9 @@ static int hub_configure(struct usb_hub *hub, dev_dbg(hub_dev, "%sover-current condition exists\n", (hubstatus & HUB_STATUS_OVERCURRENT) ? "" : "no "); + /* scan all ports ASAP on new hubs */ + hub->change_bits[0] = ~0; + /* Start the interrupt endpoint */ pipe = usb_rcvintpipe(hdev, endpoint->bEndpointAddress); maxp = usb_maxpacket(hdev, pipe, usb_pipeout(pipe)); @@ -604,7 +616,7 @@ static int hub_configure(struct usb_hub *hub, } /* Wake up khubd */ - wake_up(&khubd_wait); + kick_khubd(hub); /* maybe start cycling the hub leds */ if (hub->has_indicators && blinkenlights) { @@ -1408,15 +1420,18 @@ static void hub_port_logical_disconnect(struct usb_device *hdev, int port) dev_dbg(hubdev(hdev), "logical disconnect on port %d\n", port + 1); hub_port_disable(hdev, port); + /* FIXME let caller ask to power down the port: + * - some devices won't enumerate without a VBUS power cycle + * - SRP saves power that way + * - usb_suspend_device(dev,PM_SUSPEND_DISK) + * That's easy if this hub can switch power per-port, and + * khubd reactivates the port later (timer, SRP, etc). + * Powerdown must be optional, because of reset/DFU. + */ + hub = usb_get_intfdata(hdev->actconfig->interface[0]); set_bit(port, hub->change_bits); - - spin_lock_irq(&hub_event_lock); - if (list_empty(&hub->event_list)) { - list_add_tail(&hub->event_list, &hub_event_list); - wake_up(&khubd_wait); - } - spin_unlock_irq(&hub_event_lock); + kick_khubd(hub); } -- cgit v1.2.3 From 10f249cf7f98600d2984b14524e7885bca786516 Mon Sep 17 00:00:00 2001 From: David Brownell Date: Thu, 9 Sep 2004 02:46:47 -0700 Subject: [PATCH] USB: omap_udc supports 5910/1510 chips This removes the "don't run on OMAP 1510" constraint from the driver and teaches it how not to use registers only available on newer chips. It's effectively just a compile fix, though ... hasn't got any board support yet. Basic build fixes for the OMAP 1510/5910. This first generation chip doesn't include an OTG controller. The patch makes the driver stop using OTG registers, and adds some #defines that will be needed to support boards using those chips. Signed-off-by: David Brownell Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/omap_udc.c | 144 +++++++++++++++++++++++------------------- drivers/usb/gadget/omap_udc.h | 11 +++- 2 files changed, 89 insertions(+), 66 deletions(-) diff --git a/drivers/usb/gadget/omap_udc.c b/drivers/usb/gadget/omap_udc.c index e40089d79365..84de1faeab64 100644 --- a/drivers/usb/gadget/omap_udc.c +++ b/drivers/usb/gadget/omap_udc.c @@ -1200,7 +1200,8 @@ static void pullup_enable(struct omap_udc *udc) { UDC_SYSCON1_REG |= UDC_PULLUP_EN; #ifndef CONFIG_USB_OTG - OTG_CTRL_REG |= OTG_BSESSVLD; + if (!cpu_is_omap15xx()) + OTG_CTRL_REG |= OTG_BSESSVLD; #endif UDC_IRQ_EN_REG = UDC_DS_CHG_IE; } @@ -1208,7 +1209,8 @@ static void pullup_enable(struct omap_udc *udc) static void pullup_disable(struct omap_udc *udc) { #ifndef CONFIG_USB_OTG - OTG_CTRL_REG &= ~OTG_BSESSVLD; + if (!cpu_is_omap15xx()) + OTG_CTRL_REG &= ~OTG_BSESSVLD; #endif UDC_IRQ_EN_REG = UDC_DS_CHG_IE; UDC_SYSCON1_REG &= ~UDC_PULLUP_EN; @@ -1688,7 +1690,7 @@ static void devstate_irq(struct omap_udc *udc, u16 irq_src) } change &= ~UDC_SUS; } - if (change & OTG_FLAGS) { + if (!cpu_is_omap15xx() && (change & OTG_FLAGS)) { update_otg(udc); change &= ~OTG_FLAGS; } @@ -2036,34 +2038,14 @@ static char *trx_mode(unsigned m) } } -static int proc_udc_show(struct seq_file *s, void *_) +static int proc_otg_show(struct seq_file *s) { u32 tmp; - struct omap_ep *ep; - unsigned long flags; - - spin_lock_irqsave(&udc->lock, flags); - seq_printf(s, "%s, version: " DRIVER_VERSION -#ifdef USE_ISO - " (iso)" -#endif - "%s\n", - driver_desc, - use_dma ? " (dma)" : ""); - - tmp = UDC_REV_REG & 0xff; - seq_printf(s, - "UDC rev %d.%d, OTG rev %d.%d, fifo mode %d, gadget %s\n" - "hmc %d, transceiver %08x %s\n", + tmp = OTG_REV_REG; + seq_printf(s, "OTG rev %d.%d, transceiver_ctrl %08x\n", tmp >> 4, tmp & 0xf, - OTG_REV_REG >> 4, OTG_REV_REG & 0xf, - fifo_mode, - udc->driver ? udc->driver->driver.name : "(none)", - HMC, USB_TRANSCEIVER_CTRL_REG, - udc->transceiver ? udc->transceiver->label : ""); - - /* OTG controller registers */ + USB_TRANSCEIVER_CTRL_REG); tmp = OTG_SYSCON_1_REG; seq_printf(s, "otg_syscon1 %08x usb2 %s, usb1 %s, usb0 %s," FOURBITS "\n", tmp, @@ -2117,6 +2099,37 @@ static int proc_udc_show(struct seq_file *s, void *_) seq_printf(s, "otg_outctrl %04x" "\n", tmp); tmp = OTG_TEST_REG; seq_printf(s, "otg_test %04x" "\n", tmp); +} + +static int proc_udc_show(struct seq_file *s, void *_) +{ + u32 tmp; + struct omap_ep *ep; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + seq_printf(s, "%s, version: " DRIVER_VERSION +#ifdef USE_ISO + " (iso)" +#endif + "%s\n", + driver_desc, + use_dma ? " (dma)" : ""); + + tmp = UDC_REV_REG & 0xff; + seq_printf(s, + "UDC rev %d.%d, fifo mode %d, gadget %s\n" + "hmc %d, transceiver %s\n", + tmp >> 4, tmp & 0xf, + fifo_mode, + udc->driver ? udc->driver->driver.name : "(none)", + HMC, + udc->transceiver ? udc->transceiver->label : ""); + + /* OTG controller registers */ + if (!cpu_is_omap15xx()) + proc_otg_show(s); tmp = UDC_SYSCON1_REG; seq_printf(s, "\nsyscon1 %04x" EIGHTBITS "\n", tmp, @@ -2496,41 +2509,51 @@ static int __init omap_udc_probe(struct device *dev) return -EBUSY; } - INFO("OMAP UDC rev %d.%d, OTG rev %d.%d, %s receptacle\n", + INFO("OMAP UDC rev %d.%d, %s receptacle\n", UDC_REV_REG >> 4, UDC_REV_REG & 0xf, - OTG_REV_REG >> 4, OTG_REV_REG & 0xf, config->otg ? "Mini-AB" : "B/Mini-B"); /* use the mode given to us by board init code */ - hmc = HMC; - switch (hmc) { - case 3: - case 11: - case 19: - case 25: - xceiv = otg_get_transceiver(); - if (!xceiv) { - DBG("external transceiver not registered!\n"); - goto cleanup0; - } - type = xceiv->label; - break; - case 0: /* POWERUP DEFAULT == 0 */ - case 4: - case 12: - case 20: - type = "INTEGRATED"; - break; - case 21: /* internal loopback */ - type = "(loopback)"; - break; - case 14: /* transceiverless */ - type = "(none)"; - break; + if (cpu_is_omap15xx()) { + hmc = HMC_1510; + type = "(unknown)"; - default: - ERR("unrecognized UDC HMC mode %d\n", hmc); - return -ENODEV; + /* FIXME may need a GPIO-0 handler to call + * usb_gadget_vbus_{dis,}connect() on us... + */ + } else { + hmc = HMC_1610; + switch (hmc) { + case 3: + case 11: + case 19: + case 25: + xceiv = otg_get_transceiver(); + if (!xceiv) { + DBG("external transceiver not registered!\n"); + if (config->otg) + goto cleanup0; + type = "(unknown external)"; + } else + type = xceiv->label; + break; + case 0: /* POWERUP DEFAULT == 0 */ + case 4: + case 12: + case 20: + type = "INTEGRATED"; + break; + case 21: /* internal loopback */ + type = "(loopback)"; + break; + case 14: /* transceiverless */ + type = "(none)"; + break; + + default: + ERR("unrecognized UDC HMC mode %d\n", hmc); + return -ENODEV; + } } INFO("hmc mode %d, transceiver %s\n", hmc, type); @@ -2671,13 +2694,6 @@ static struct device_driver udc_driver = { static int __init udc_init(void) { - /* should work on many OMAP systems with at most minor changes, - * but the 1510 doesn't have an OTG controller. - */ - if (cpu_is_omap1510()) { - DBG("no OMAP1510 support yet\n"); - return -ENODEV; - } INFO("%s, version: " DRIVER_VERSION "%s\n", driver_desc, use_dma ? " (dma)" : ""); return driver_register(&udc_driver); diff --git a/drivers/usb/gadget/omap_udc.h b/drivers/usb/gadget/omap_udc.h index bd5420cd0b05..ca8572314f95 100644 --- a/drivers/usb/gadget/omap_udc.h +++ b/drivers/usb/gadget/omap_udc.h @@ -193,7 +193,14 @@ struct omap_udc { /*-------------------------------------------------------------------------*/ -// #define HMC_1510 ((MOD_CONF_CTRL_0_REG >> 1) & 0x3f) +#define MOD_CONF_CTRL_0_REG __REG32(MOD_CONF_CTRL_0) +#define VBUS_W2FC_1510 (1 << 17) /* 0 gpio0, 1 dvdd2 pin */ + +#define FUNC_MUX_CTRL_0_REG __REG32(FUNC_MUX_CTRL_0) +#define VBUS_CTRL_1510 (1 << 19) /* 1 connected (software) */ +#define VBUS_MODE_1510 (1 << 18) /* 0 hardware, 1 software */ + +#define HMC_1510 ((MOD_CONF_CTRL_0_REG >> 1) & 0x3f) #define HMC_1610 (OTG_SYSCON_2_REG & 0x3f) -#define HMC HMC_1610 +#define HMC (cpu_is_omap15xx() ? HMC_1510 : HMC_1610) -- cgit v1.2.3 From 474208be127146e70953b25be3ad8d90cdbcf001 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Thu, 9 Sep 2004 20:05:02 -0700 Subject: [PATCH] USB: maintainership of acm cdc I've discussed this with Vojtech. If I brake the probe method, I can also break the rest ;-) Signed-Off-By: Oliver Neukum Signed-off-by: Greg Kroah-Hartman - new cdc acm maintainer --- MAINTAINERS | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 09b549d488e1..34a50190f7d8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -947,7 +947,7 @@ S: Maintained HPUSBSCSI P: Oliver Neukum -M: drivers@neukum.org +M: oliver@neukum.name S: Maintained I2C AND SENSORS DRIVERS @@ -1428,7 +1428,7 @@ S: Maintained MICROTEK X6 SCANNER P: Oliver Neukum -M: drivers@neukum.org +M: oliver@neukum.name S: Maintained MIPS @@ -2184,8 +2184,8 @@ W: http://www.kernel.dk S: Maintained USB ACM DRIVER -P: Vojtech Pavlik -M: vojtech@suse.cz +P: Oliver Neukum +M: oliver@neukum.name L: linux-usb-users@lists.sourceforge.net L: linux-usb-devel@lists.sourceforge.net S: Maintained @@ -2229,7 +2229,7 @@ S: Maintained USB KAWASAKI LSI DRIVER P: Oliver Neukum -M: drivers@neukum.org +M: oliver@neukum.name L: linux-usb-users@lists.sourceforge.net L: linux-usb-devel@lists.sourceforge.net S: Maintained -- cgit v1.2.3 From a83b8e193a0302f30a6df3f24bfef62c0ebc2e88 Mon Sep 17 00:00:00 2001 From: John Stultz Date: Fri, 10 Sep 2004 01:02:06 -0700 Subject: [PATCH] USB: early usb handoff for 2.6 Apparently there is an issue w/ the IBM x440/x445's BIOS's USB Legacy support. Due to the delay in issuing SMI's across the IOAPICs, its possible for I/O to ports 60/64 to cause register corruption. The solution is to disable the BIOS's USB Legacy support early in boot(via PCI quirks) for x440/x445 systems. Originally written by Vojtech against SuSE's tree, this patch was then updated to disable EHCI by Aleksey Gorelov, cleaned up by Pete Zaitcev for 2.4 and finally tweaked and updated against 2.6 by me. I've lightly tested this version of the patch, but it differs little from what Aleksey, Pete and I have been testing for 2.4. Signed-off-by: John Stultz Signed-off-by: Greg Kroah-Hartman --- Documentation/kernel-parameters.txt | 2 + drivers/pci/quirks.c | 230 ++++++++++++++++++++++++++++ include/asm-i386/mach-summit/mach_mpparse.h | 3 + 3 files changed, 235 insertions(+) diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index ae0eb1a3d4e4..393986feb77e 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -1270,6 +1270,8 @@ running once the system is up. uart6850= [HW,OSS] Format: , + + usb-handoff [HW] Enable early USB BIOS -> OS handoff video= [FB] Frame buffer configuration See Documentation/fb/modedb.txt. diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 164f738df6a7..34f072c9823e 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -826,6 +826,236 @@ static void __init quirk_sis_96x_smbus(struct pci_dev *dev) pci_read_config_byte(dev, 0x77, &val); } + +#define UHCI_USBLEGSUP 0xc0 /* legacy support */ +#define UHCI_USBCMD 0 /* command register */ +#define UHCI_USBSTS 2 /* status register */ +#define UHCI_USBINTR 4 /* interrupt register */ +#define UHCI_USBLEGSUP_DEFAULT 0x2000 /* only PIRQ enable set */ +#define UHCI_USBCMD_RUN (1 << 0) /* RUN/STOP bit */ +#define UHCI_USBCMD_GRESET (1 << 2) /* Global reset */ +#define UHCI_USBCMD_CONFIGURE (1 << 6) /* config semaphore */ +#define UHCI_USBSTS_HALTED (1 << 5) /* HCHalted bit */ + +#define OHCI_CONTROL 0x04 +#define OHCI_CMDSTATUS 0x08 +#define OHCI_INTRSTATUS 0x0c +#define OHCI_INTRENABLE 0x10 +#define OHCI_INTRDISABLE 0x14 +#define OHCI_OCR (1 << 3) /* ownership change request */ +#define OHCI_CTRL_IR (1 << 8) /* interrupt routing */ +#define OHCI_INTR_OC (1 << 30) /* ownership change */ + +#define EHCI_HCC_PARAMS 0x08 /* extended capabilities */ +#define EHCI_USBCMD 0 /* command register */ +#define EHCI_USBCMD_RUN (1 << 0) /* RUN/STOP bit */ +#define EHCI_USBSTS 4 /* status register */ +#define EHCI_USBSTS_HALTED (1 << 12) /* HCHalted bit */ +#define EHCI_USBINTR 8 /* interrupt register */ +#define EHCI_USBLEGSUP 0 /* legacy support register */ +#define EHCI_USBLEGSUP_BIOS (1 << 16) /* BIOS semaphore */ +#define EHCI_USBLEGSUP_OS (1 << 24) /* OS semaphore */ +#define EHCI_USBLEGCTLSTS 4 /* legacy control/status */ +#define EHCI_USBLEGCTLSTS_SOOE (1 << 13) /* SMI on ownership change */ + +int usb_early_handoff __initdata = 0; +static int __init usb_handoff_early(char *str) +{ + usb_early_handoff = 1; + return 0; +} +__setup("usb-handoff", usb_handoff_early); + +static void __init quirk_usb_handoff_uhci(struct pci_dev *pdev) +{ + unsigned long base = 0; + int wait_time, delta; + u16 val, sts; + int i; + + for (i = 0; i < PCI_ROM_RESOURCE; i++) + if ((pci_resource_flags(pdev, i) & IORESOURCE_IO)) { + base = pci_resource_start(pdev, i); + break; + } + + if (!base) + return; + + /* + * stop controller + */ + sts = inw(base + UHCI_USBSTS); + val = inw(base + UHCI_USBCMD); + val &= ~(u16)(UHCI_USBCMD_RUN | UHCI_USBCMD_CONFIGURE); + outw(val, base + UHCI_USBCMD); + + /* + * wait while it stops if it was running + */ + if ((sts & UHCI_USBSTS_HALTED) == 0) + { + wait_time = 1000; + delta = 100; + + do { + outw(0x1f, base + UHCI_USBSTS); + udelay(delta); + wait_time -= delta; + val = inw(base + UHCI_USBSTS); + if (val & UHCI_USBSTS_HALTED) + break; + } while (wait_time > 0); + } + + /* + * disable interrupts & legacy support + */ + outw(0, base + UHCI_USBINTR); + outw(0x1f, base + UHCI_USBSTS); + pci_read_config_word(pdev, UHCI_USBLEGSUP, &val); + if (val & 0xbf) + pci_write_config_word(pdev, UHCI_USBLEGSUP, UHCI_USBLEGSUP_DEFAULT); + +} + +static void __init quirk_usb_handoff_ohci(struct pci_dev *pdev) +{ + char *base; + int wait_time; + + base = ioremap_nocache(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + if (base == NULL) return; + + if (readl(base + OHCI_CONTROL) & OHCI_CTRL_IR) { + wait_time = 500; /* 0.5 seconds */ + writel(OHCI_INTR_OC, base + OHCI_INTRENABLE); + writel(OHCI_OCR, base + OHCI_CMDSTATUS); + while (wait_time > 0 && + readl(base + OHCI_CONTROL) & OHCI_CTRL_IR) { + wait_time -= 10; + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout((HZ*10 + 999) / 1000); + } + } + + /* + * disable interrupts + */ + writel(~(u32)0, base + OHCI_INTRDISABLE); + writel(~(u32)0, base + OHCI_INTRSTATUS); + + iounmap(base); +} + +static void __init quirk_usb_disable_ehci(struct pci_dev *pdev) +{ + int wait_time, delta; + char *base, *op_reg_base; + u32 hcc_params, val, temp; + u8 cap_length; + + base = ioremap_nocache(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + if (base == NULL) return; + + cap_length = readb(base); + op_reg_base = base + cap_length; + hcc_params = readl(base + EHCI_HCC_PARAMS); + hcc_params = (hcc_params >> 8) & 0xff; + if (hcc_params) { + pci_read_config_dword(pdev, + hcc_params + EHCI_USBLEGSUP, + &val); + if (((val & 0xff) == 1) && (val & EHCI_USBLEGSUP_BIOS)) { + /* + * Ok, BIOS is in smm mode, try to hand off... + */ + pci_read_config_dword(pdev, + hcc_params + EHCI_USBLEGCTLSTS, + &temp); + pci_write_config_dword(pdev, + hcc_params + EHCI_USBLEGCTLSTS, + temp | EHCI_USBLEGCTLSTS_SOOE); + val |= EHCI_USBLEGSUP_OS; + pci_write_config_dword(pdev, + hcc_params + EHCI_USBLEGSUP, + val); + + wait_time = 500; + do { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout((HZ*10+999)/1000); + wait_time -= 10; + pci_read_config_dword(pdev, + hcc_params + EHCI_USBLEGSUP, + &val); + } while (wait_time && (val & EHCI_USBLEGSUP_BIOS)); + if (!wait_time) { + /* + * well, possibly buggy BIOS... + */ + printk(KERN_WARNING "EHCI early BIOS handoff " + "failed (BIOS bug ?)\n"); + pci_write_config_dword(pdev, + hcc_params + EHCI_USBLEGSUP, + EHCI_USBLEGSUP_OS); + pci_write_config_dword(pdev, + hcc_params + EHCI_USBLEGCTLSTS, + 0); + } + } + } + + /* + * halt EHCI & disable its interrupts in any case + */ + val = readl(op_reg_base + EHCI_USBSTS); + if ((val & EHCI_USBSTS_HALTED) == 0) { + val = readl(op_reg_base + EHCI_USBCMD); + val &= ~EHCI_USBCMD_RUN; + writel(val, op_reg_base + EHCI_USBCMD); + + wait_time = 2000; + delta = 100; + do { + writel(0x3f, op_reg_base + EHCI_USBSTS); + udelay(delta); + wait_time -= delta; + val = readl(op_reg_base + EHCI_USBSTS); + if ((val == ~(u32)0) || (val & EHCI_USBSTS_HALTED)) { + break; + } + } while (wait_time > 0); + } + writel(0, op_reg_base + EHCI_USBINTR); + writel(0x3f, op_reg_base + EHCI_USBSTS); + + iounmap(base); + + return; +} + + + +static void __init quirk_usb_early_handoff(struct pci_dev *pdev) +{ + if (!usb_early_handoff) + return; + + if (pdev->class == ((PCI_CLASS_SERIAL_USB << 8) | 0x00)) { /* UHCI */ + quirk_usb_handoff_uhci(pdev); + } else if (pdev->class == ((PCI_CLASS_SERIAL_USB << 8) | 0x10)) { /* OHCI */ + quirk_usb_handoff_ohci(pdev); + } else if (pdev->class == ((PCI_CLASS_SERIAL_USB << 8) | 0x20)) { /* EHCI */ + quirk_usb_disable_ehci(pdev); + } + + return; +} +DECLARE_PCI_FIXUP_HEADER(PCI_ANY_ID, PCI_ANY_ID, quirk_usb_early_handoff); + /* * ... This is further complicated by the fact that some SiS96x south * bridges pretend to be 85C503/5513 instead. In that case see if we diff --git a/include/asm-i386/mach-summit/mach_mpparse.h b/include/asm-i386/mach-summit/mach_mpparse.h index 1cce2b924a80..2b9e6d55bef1 100644 --- a/include/asm-i386/mach-summit/mach_mpparse.h +++ b/include/asm-i386/mach-summit/mach_mpparse.h @@ -22,6 +22,7 @@ static inline void mpc_oem_pci_bus(struct mpc_config_bus *m, { } +extern int usb_early_handoff; static inline int mps_oem_check(struct mp_config_table *mpc, char *oem, char *productid) { @@ -31,6 +32,7 @@ static inline int mps_oem_check(struct mp_config_table *mpc, char *oem, || !strncmp(productid, "RUTHLESS SMP", 12))){ use_cyclone = 1; /*enable cyclone-timer*/ setup_summit(); + usb_early_handoff = 1; return 1; } return 0; @@ -44,6 +46,7 @@ static inline int acpi_madt_oem_check(char *oem_id, char *oem_table_id) || !strncmp(oem_table_id, "EXA", 3))){ use_cyclone = 1; /*enable cyclone-timer*/ setup_summit(); + usb_early_handoff = 1; return 1; } return 0; -- cgit v1.2.3 From 6a51d280116e0ce69b8105242c61047c690ff49d Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Fri, 10 Sep 2004 01:02:34 -0700 Subject: [PATCH] USB: Improve UHCI suspend/resume This patch fixes some problems with the suspend/resume handling in the UHCI driver: Don't try to resume ports that aren't already suspended. The controller won't permit Resume signals to be sent to a port if the port isn't enabled, so don't assume it will work. Port reset and port disable will terminate Resume signalling. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/uhci-hub.c | 67 +++++++++++++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/drivers/usb/host/uhci-hub.c b/drivers/usb/host/uhci-hub.c index e869ad4061f1..538be7c5a409 100644 --- a/drivers/usb/host/uhci-hub.c +++ b/drivers/usb/host/uhci-hub.c @@ -66,11 +66,23 @@ static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf) * so we have to poll and check timeouts in order to take care of it. * FIXME: Synchronize access to these fields by a spinlock. */ +static void uhci_finish_suspend(struct uhci_hcd *uhci, int port, + unsigned int port_addr) +{ + int status; + + if (test_bit(port, &uhci->suspended_ports)) { + CLR_RH_PORTSTAT(USBPORTSC_SUSP | USBPORTSC_RD); + clear_bit(port, &uhci->suspended_ports); + clear_bit(port, &uhci->resuming_ports); + set_bit(port, &uhci->port_c_suspend); + } +} + static void uhci_check_resume(struct uhci_hcd *uhci) { unsigned int port; unsigned int port_addr; - int status; for (port = 0; port < uhci->rh_numports; ++port) { port_addr = uhci->io_addr + USBPORTSC1 + 2 * port; @@ -83,10 +95,7 @@ static void uhci_check_resume(struct uhci_hcd *uhci) msecs_to_jiffies(20); } else if (time_after_eq(jiffies, uhci->resume_timeout)) { - CLR_RH_PORTSTAT(USBPORTSC_SUSP | USBPORTSC_RD); - clear_bit(port, &uhci->resuming_ports); - clear_bit(port, &uhci->suspended_ports); - set_bit(port, &uhci->port_c_suspend); + uhci_finish_suspend(uhci, port, port_addr); } } } @@ -97,19 +106,12 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, char *buf, u16 wLength) { struct uhci_hcd *uhci = hcd_to_uhci(hcd); - int status, retval = 0, len = 0; + int status, lstatus, retval = 0, len = 0; unsigned int port = wIndex - 1; unsigned long port_addr = uhci->io_addr + USBPORTSC1 + 2 * port; u16 wPortChange, wPortStatus; switch (typeReq) { - /* Request Destination: - without flags: Device, - RH_INTERFACE: interface, - RH_ENDPOINT: endpoint, - RH_CLASS means HUB here, - RH_OTHER | RH_CLASS almost ever means HUB_PORT here - */ case GetHubStatus: *(u32 *) buf = cpu_to_le32(0); @@ -132,15 +134,22 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, status ^= USBPORTSC_OC; /* UHCI doesn't support C_RESET (always false) */ - wPortChange = 0; + wPortChange = lstatus = 0; if (status & USBPORTSC_CSC) wPortChange |= USB_PORT_STAT_C_CONNECTION; if (status & USBPORTSC_PEC) wPortChange |= USB_PORT_STAT_C_ENABLE; if (status & USBPORTSC_OCC) wPortChange |= USB_PORT_STAT_C_OVERCURRENT; - if (test_bit(port, &uhci->port_c_suspend)) + + if (test_bit(port, &uhci->port_c_suspend)) { wPortChange |= USB_PORT_STAT_C_SUSPEND; + lstatus |= 1; + } + if (test_bit(port, &uhci->suspended_ports)) + lstatus |= 2; + if (test_bit(port, &uhci->resuming_ports)) + lstatus |= 4; /* UHCI has no power switching (always on) */ wPortStatus = USB_PORT_STAT_POWER; @@ -159,8 +168,8 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, wPortStatus |= USB_PORT_STAT_LOW_SPEED; if (wPortChange) - dev_dbg(uhci_dev(uhci), "port %d portsc %04x\n", - wIndex, status); + dev_dbg(uhci_dev(uhci), "port %d portsc %04x,%02x\n", + wIndex, status, lstatus); *(u16 *) buf = cpu_to_le16(wPortStatus); *(u16 *) (buf + 2) = cpu_to_le16(wPortChange); @@ -189,6 +198,9 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, mdelay(50); /* USB v1.1 7.1.7.3 */ CLR_RH_PORTSTAT(USBPORTSC_PR); udelay(10); + + /* Reset terminates Resume signalling */ + uhci_finish_suspend(uhci, port, port_addr); SET_RH_PORTSTAT(USBPORTSC_PE); mdelay(10); CLR_RH_PORTSTAT(USBPORTSC_PEC|USBPORTSC_CSC); @@ -207,14 +219,29 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, switch (wValue) { case USB_PORT_FEAT_ENABLE: CLR_RH_PORTSTAT(USBPORTSC_PE); + + /* Disable terminates Resume signalling */ + uhci_finish_suspend(uhci, port, port_addr); OK(0); case USB_PORT_FEAT_C_ENABLE: CLR_RH_PORTSTAT(USBPORTSC_PEC); OK(0); case USB_PORT_FEAT_SUSPEND: - set_bit(port, &uhci->resuming_ports); - uhci->resume_timeout = jiffies + msecs_to_jiffies(20); - SET_RH_PORTSTAT(USBPORTSC_RD); + if (test_bit(port, &uhci->suspended_ports) && + !test_and_set_bit(port, + &uhci->resuming_ports)) { + uhci->resume_timeout = jiffies + + msecs_to_jiffies(20); + SET_RH_PORTSTAT(USBPORTSC_RD); + + /* The controller won't allow RD to be set + * if the port is disabled. When this happens + * just skip the Resume signalling. + */ + if (!(inw(port_addr) & USBPORTSC_RD)) + uhci_finish_suspend(uhci, port, + port_addr); + } OK(0); case USB_PORT_FEAT_C_SUSPEND: clear_bit(port, &uhci->port_c_suspend); -- cgit v1.2.3 From 26d61c640c05c6cf04987437fad260908faaddf7 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Fri, 10 Sep 2004 01:04:14 -0700 Subject: [PATCH] USB: Fix off-by-one error in the hub driver This one-liner fixes a simple mistake in a newly-added part of the hub driver. When a connect change takes place on a suspended port the code clears the suspend, but the port number it passes is origin-0 and it should be origin-1. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 13879305bf01..29bba5e78da3 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2050,7 +2050,7 @@ hub_port_init (struct usb_device *hdev, struct usb_device *udev, int port) hdev->bus->b_hnp_enable = 0; } - retval = clear_port_feature(hdev, port, USB_PORT_FEAT_SUSPEND); + retval = clear_port_feature(hdev, port + 1, USB_PORT_FEAT_SUSPEND); if (retval < 0 && retval != -EPIPE) dev_dbg(&udev->dev, "can't clear suspend; %d\n", retval); -- cgit v1.2.3 From fa4b40f4ef571816068e6d08b174d0a5c713ad36 Mon Sep 17 00:00:00 2001 From: David Brownell Date: Mon, 13 Sep 2004 01:55:23 -0700 Subject: [PATCH] USB: ohci init refactor Please merge, some recent changes made problems by making init take too long. This also adds a bit of support for detecting the funky resume states that happen with suspend-to-disk (like swsusp, pmdisk). Refactor controller initialization ... this is most of the patch by volume. - A time-critical section now runs with IRQs blocked, rather than being split over two separate routines. (I've recently seen init failures because of preemption in the middle of that 2msec timeout, presumably by khubd.) - Bus glue for PCI, LH7A404, OMAP, and SA-1100 now shares more init logic; that'll help shrink support for upcoming non-PCI patches too. - Move the root hub register macros to the header (for debug build issue) - More tweaks to the frame clock initialization, including slightly more helpful diagnostics on "init err". Better SWSUSP support. - Detects and handles some funky "resume after suspend-to-disk" cases. These need to go through full driver re-init. - Restore root hub to CONFIGURED state on resume. Signed-off-by: David Brownell Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-hcd.c | 173 ++++++++++++++++++++++++++-------------- drivers/usb/host/ohci-hub.c | 46 +++-------- drivers/usb/host/ohci-lh7a404.c | 30 +------ drivers/usb/host/ohci-omap.c | 30 +------ drivers/usb/host/ohci-pci.c | 34 ++------ drivers/usb/host/ohci-sa1111.c | 25 +----- drivers/usb/host/ohci.h | 30 ++++++- 7 files changed, 167 insertions(+), 201 deletions(-) diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index d96a4edbd022..c5b806265504 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -138,6 +138,11 @@ static const char hcd_name [] = "ohci_hcd"; #include "ohci.h" +static void ohci_dump (struct ohci_hcd *ohci, int verbose); +static int ohci_init (struct ohci_hcd *ohci); +static int ohci_restart (struct ohci_hcd *ohci); +static void ohci_stop (struct usb_hcd *hcd); + #include "ohci-hub.c" #include "ohci-dbg.c" #include "ohci-mem.c" @@ -397,33 +402,27 @@ static int ohci_get_frame (struct usb_hcd *hcd) return OHCI_FRAME_NO(ohci->hcca); } +static void ohci_usb_reset (struct ohci_hcd *ohci) +{ + ohci->hc_control = ohci_readl (&ohci->regs->control); + ohci->hc_control &= OHCI_CTRL_RWC; + writel (ohci->hc_control, &ohci->regs->control); +} + /*-------------------------------------------------------------------------* * HC functions *-------------------------------------------------------------------------*/ -/* reset the HC and BUS */ +/* init memory, and kick BIOS/SMM off */ -static int hc_reset (struct ohci_hcd *ohci) +static int ohci_init (struct ohci_hcd *ohci) { u32 temp; + int ret; - /* boot firmware should have set this up (5.1.1.3.1) */ - if (!ohci->fminterval) { - u32 t2; - - temp = ohci_readl (&ohci->regs->fminterval); - ohci->fminterval = temp & 0x3fff; - if (ohci->fminterval != FI) - ohci_dbg (ohci, "fminterval delta %d\n", - ohci->fminterval - FI); - - t2 = FSMP (ohci->fminterval); - temp >>= 16; - if ((t2/2) < temp || temp > t2) - temp = t2; - ohci->fminterval |= temp << 16; - /* also: power/overcurrent flags in roothub.a */ - } + disable (ohci); + ohci->regs = ohci->hcd.regs; + ohci->next_statechange = jiffies; #ifndef IR_DISABLE /* SMM owns the HC? not for long! */ @@ -442,14 +441,60 @@ static int hc_reset (struct ohci_hcd *ohci) msleep (10); if (--temp == 0) { ohci_err (ohci, "USB HC TakeOver failed!\n"); - return -1; + return -EBUSY; } } + ohci_usb_reset (ohci); } #endif /* Disable HC interrupts */ writel (OHCI_INTR_MIE, &ohci->regs->intrdisable); + // flush the writes + (void) ohci_readl (&ohci->regs->control); + + if (ohci->hcca) + return 0; + + ohci->hcca = dma_alloc_coherent (ohci->hcd.self.controller, + sizeof *ohci->hcca, &ohci->hcca_dma, 0); + if (!ohci->hcca) + return -ENOMEM; + + if ((ret = ohci_mem_init (ohci)) < 0) + ohci_stop (&ohci->hcd); + + return ret; + +} + +/*-------------------------------------------------------------------------*/ + +/* Start an OHCI controller, set the BUS operational + * resets USB and controller + * enable interrupts + * connect the virtual root hub + */ +static int ohci_run (struct ohci_hcd *ohci) +{ + u32 mask, temp; + struct usb_device *udev; + struct usb_bus *bus; + int first = ohci->fminterval == 0; + + disable (ohci); + + /* boot firmware should have set this up (5.1.1.3.1) */ + if (first) { + + temp = ohci_readl (&ohci->regs->fminterval); + ohci->fminterval = temp & 0x3fff; + if (ohci->fminterval != FI) + ohci_dbg (ohci, "fminterval delta %d\n", + ohci->fminterval - FI); + ohci->fminterval |= FSMP (ohci->fminterval) << 16; + /* also: power/overcurrent flags in roothub.a */ + } /* Reset USB nearly "by the book". RemoteWakeupConnected * saved if boot firmware (BIOS/SMM/...) told us it's connected @@ -495,18 +540,22 @@ static int hc_reset (struct ohci_hcd *ohci) } // flush those writes (void) ohci_readl (&ohci->regs->control); + memset (ohci->hcca, 0, sizeof (struct ohci_hcca)); + + /* 2msec timelimit here means no irqs/preempt */ + spin_lock_irq (&ohci->lock); /* HC Reset requires max 10 us delay */ writel (OHCI_HCR, &ohci->regs->cmdstatus); temp = 30; /* ... allow extra time */ while ((ohci_readl (&ohci->regs->cmdstatus) & OHCI_HCR) != 0) { if (--temp == 0) { + spin_unlock_irq (&ohci->lock); ohci_err (ohci, "USB HC reset timed out!\n"); return -1; } udelay (1); } - periodic_reinit (ohci); /* now we're in the SUSPEND state ... must go OPERATIONAL * within 2msec else HC enters RESUME @@ -520,22 +569,7 @@ static int hc_reset (struct ohci_hcd *ohci) // flush those writes (void) ohci_readl (&ohci->regs->control); } - return 0; -} - -/*-------------------------------------------------------------------------*/ - -/* Start an OHCI controller, set the BUS operational - * enable interrupts - * connect the virtual root hub - */ -static int hc_start (struct ohci_hcd *ohci) -{ - u32 mask, tmp; - struct usb_device *udev; - struct usb_bus *bus; - - disable (ohci); + writel (ohci->fminterval, &ohci->regs->fminterval); /* Tell the controller where the control and bulk lists are * The lists are empty now. */ @@ -545,12 +579,17 @@ static int hc_start (struct ohci_hcd *ohci) /* a reset clears this */ writel ((u32) ohci->hcca_dma, &ohci->regs->hcca); + periodic_reinit (ohci); + /* some OHCI implementations are finicky about how they init. * bogus values here mean not even enumeration could work. */ if ((ohci_readl (&ohci->regs->fminterval) & 0x3fff0000) == 0 || !ohci_readl (&ohci->regs->periodicstart)) { - ohci_err (ohci, "init err\n"); + spin_unlock_irq (&ohci->lock); + ohci_err (ohci, "init err (%08x %04x)\n", + ohci_readl (&ohci->regs->fminterval), + ohci_readl (&ohci->regs->periodicstart)); return -EOVERFLOW; } @@ -569,42 +608,48 @@ static int hc_start (struct ohci_hcd *ohci) writel (mask, &ohci->regs->intrenable); /* handle root hub init quirks ... */ - tmp = roothub_a (ohci); - tmp &= ~(RH_A_PSM | RH_A_OCPM); + temp = roothub_a (ohci); + temp &= ~(RH_A_PSM | RH_A_OCPM); if (ohci->flags & OHCI_QUIRK_SUPERIO) { /* NSC 87560 and maybe others */ - tmp |= RH_A_NOCP; - tmp &= ~(RH_A_POTPGT | RH_A_NPS); + temp |= RH_A_NOCP; + temp &= ~(RH_A_POTPGT | RH_A_NPS); } else if (power_switching) { /* act like most external hubs: use per-port power * switching and overcurrent reporting. */ - tmp &= ~(RH_A_NPS | RH_A_NOCP); - tmp |= RH_A_PSM | RH_A_OCPM; + temp &= ~(RH_A_NPS | RH_A_NOCP); + temp |= RH_A_PSM | RH_A_OCPM; } else { /* hub power always on; required for AMD-756 and some * Mac platforms. ganged overcurrent reporting, if any. */ - tmp |= RH_A_NPS; + temp |= RH_A_NPS; } - writel (tmp, &ohci->regs->roothub.a); + writel (temp, &ohci->regs->roothub.a); writel (RH_HS_LPSC, &ohci->regs->roothub.status); writel (power_switching ? RH_B_PPCM : 0, &ohci->regs->roothub.b); // flush those writes (void) ohci_readl (&ohci->regs->control); + spin_unlock_irq (&ohci->lock); + // POTPGT delay is bits 24-31, in 2 ms units. mdelay ((roothub_a (ohci) >> 23) & 0x1fe); bus = hcd_to_bus (&ohci->hcd); + ohci->hcd.state = USB_STATE_RUNNING; - if (bus->root_hub) { - ohci->hcd.state = USB_STATE_RUNNING; + ohci_dump (ohci, 1); + + udev = hcd_to_bus (&ohci->hcd)->root_hub; + if (udev) { + udev->dev.power.power_state = 0; + usb_set_device_state (udev, USB_STATE_CONFIGURED); return 0; } /* connect the virtual root hub */ udev = usb_alloc_dev (NULL, bus, 0); - ohci->hcd.state = USB_STATE_RUNNING; if (!udev) { disable (ohci); ohci->hc_control &= ~OHCI_CTRL_HCFS; @@ -620,7 +665,10 @@ static int hc_start (struct ohci_hcd *ohci) writel (ohci->hc_control, &ohci->regs->control); return -ENODEV; } + if (ohci->power_budget) + hub_set_power_budget(udev, ohci->power_budget); + create_debug_files (ohci); return 0; } @@ -657,8 +705,7 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd, struct pt_regs *ptregs) // e.g. due to PCI Master/Target Abort ohci_dump (ohci, 1); - ohci->hc_control &= OHCI_CTRL_RWC; /* hcfs 0 = RESET */ - writel (ohci->hc_control, &ohci->regs->control); + ohci_usb_reset (ohci); } if (ints & OHCI_INTR_RD) { @@ -712,10 +759,9 @@ static void ohci_stop (struct usb_hcd *hcd) ohci_dump (ohci, 1); flush_scheduled_work(); - if (HCD_IS_RUNNING(ohci->hcd.state)) - hc_reset (ohci); - else - writel (OHCI_INTR_MIE, &ohci->regs->intrdisable); + + ohci_usb_reset (ohci); + writel (OHCI_INTR_MIE, &ohci->regs->intrdisable); remove_debug_files (ohci); ohci_mem_cleanup (ohci); @@ -734,7 +780,7 @@ static void ohci_stop (struct usb_hcd *hcd) #if defined(CONFIG_USB_SUSPEND) || defined(CONFIG_PM) -static int hc_restart (struct ohci_hcd *ohci) +static int ohci_restart (struct ohci_hcd *ohci) { int temp; int i; @@ -791,7 +837,7 @@ static int hc_restart (struct ohci_hcd *ohci) ohci->ed_controltail = NULL; ohci->ed_bulktail = NULL; - if ((temp = hc_reset (ohci)) < 0 || (temp = hc_start (ohci)) < 0) { + if ((temp = ohci_run (ohci)) < 0) { ohci_err (ohci, "can't restart, %d\n", temp); return temp; } else { @@ -803,10 +849,7 @@ static int hc_restart (struct ohci_hcd *ohci) while (i--) writel (RH_PS_PSS, &ohci->regs->roothub.portstatus [temp]); - ohci->hcd.self.root_hub->dev.power.power_state = 0; - ohci->hcd.state = USB_STATE_RUNNING; ohci_dbg (ohci, "restart complete\n"); - ohci_dump (ohci, 1); } return 0; } @@ -843,3 +886,13 @@ MODULE_LICENSE ("GPL"); ) #error "missing bus glue for ohci-hcd" #endif + +#if !defined(HAVE_HNP) && defined(CONFIG_USB_OTG) + +#warning non-OTG configuration, too many HCDs + +static void start_hnp(struct ohci_hcd *ohci) +{ + /* "can't happen" */ +} +#endif diff --git a/drivers/usb/host/ohci-hub.c b/drivers/usb/host/ohci-hub.c index 11093ed39cb1..1f21cf815ad4 100644 --- a/drivers/usb/host/ohci-hub.c +++ b/drivers/usb/host/ohci-hub.c @@ -2,7 +2,7 @@ * OHCI HCD (Host Controller Driver) for USB. * * (C) Copyright 1999 Roman Weissgaerber - * (C) Copyright 2000-2002 David Brownell + * (C) Copyright 2000-2004 David Brownell * * This file is licenced under GPL */ @@ -11,34 +11,8 @@ /* * OHCI Root Hub ... the nonsharable stuff - * - * Registers don't need cpu_to_le32, that happens transparently */ -/* AMD-756 (D2 rev) reports corrupt register contents in some cases. - * The erratum (#4) description is incorrect. AMD's workaround waits - * till some bits (mostly reserved) are clear; ok for all revs. - */ -#define read_roothub(hc, register, mask) ({ \ - u32 temp = ohci_readl (&hc->regs->roothub.register); \ - if (temp == -1) \ - disable (hc); \ - else if (hc->flags & OHCI_QUIRK_AMD756) \ - while (temp & mask) \ - temp = ohci_readl (&hc->regs->roothub.register); \ - temp; }) - -static u32 roothub_a (struct ohci_hcd *hc) - { return read_roothub (hc, a, 0xfc0fe000); } -static inline u32 roothub_b (struct ohci_hcd *hc) - { return ohci_readl (&hc->regs->roothub.b); } -static inline u32 roothub_status (struct ohci_hcd *hc) - { return ohci_readl (&hc->regs->roothub.status); } -static u32 roothub_portstatus (struct ohci_hcd *hc, int i) - { return read_roothub (hc, portstatus [i], 0xffe0fce0); } - -/*-------------------------------------------------------------------------*/ - #define dbg_port(hc,label,num,value) \ ohci_dbg (hc, \ "%s roothub.portstatus [%d] " \ @@ -150,7 +124,7 @@ succeed: * hub from the non-usb side (PCI, SOC, etc) stopped */ root->dev.power.power_state = 3; - root->state = USB_STATE_SUSPENDED; + usb_set_device_state (root, USB_STATE_SUSPENDED); done: spin_unlock_irq (&ohci->lock); return status; @@ -164,8 +138,6 @@ static inline struct ed *find_head (struct ed *ed) return ed; } -static int hc_restart (struct ohci_hcd *ohci); - /* caller owns root->serialize */ static int ohci_hub_resume (struct usb_hcd *hcd) { @@ -181,7 +153,12 @@ static int ohci_hub_resume (struct usb_hcd *hcd) spin_lock_irq (&ohci->lock); ohci->hc_control = ohci_readl (&ohci->regs->control); - switch (ohci->hc_control & OHCI_CTRL_HCFS) { + if (ohci->hc_control & (OHCI_CTRL_IR | OHCI_SCHED_ENABLES)) { + /* this can happen after suspend-to-disk */ + ohci_dbg (ohci, "BIOS/SMM active, control %03x\n", + ohci->hc_control); + status = -EBUSY; + } else switch (ohci->hc_control & OHCI_CTRL_HCFS) { case OHCI_USB_SUSPEND: ohci->hc_control &= ~(OHCI_CTRL_HCFS|OHCI_SCHED_ENABLES); ohci->hc_control |= OHCI_USB_RESUME; @@ -203,8 +180,10 @@ static int ohci_hub_resume (struct usb_hcd *hcd) status = -EBUSY; } spin_unlock_irq (&ohci->lock); - if (status == -EBUSY) - return hc_restart (ohci); + if (status == -EBUSY) { + (void) ohci_init (ohci); + return ohci_restart (ohci); + } if (status != -EINPROGRESS) return status; @@ -261,6 +240,7 @@ static int ohci_hub_resume (struct usb_hcd *hcd) /* TRSMRCY */ msleep (10); root->dev.power.power_state = 0; + usb_set_device_state (root, USB_STATE_CONFIGURED); /* keep it alive for ~5x suspend + resume costs */ ohci->next_statechange = jiffies + msecs_to_jiffies (250); diff --git a/drivers/usb/host/ohci-lh7a404.c b/drivers/usb/host/ohci-lh7a404.c index 4e11a8eae481..1594d1e635da 100644 --- a/drivers/usb/host/ohci-lh7a404.c +++ b/drivers/usb/host/ohci-lh7a404.c @@ -229,38 +229,14 @@ ohci_lh7a404_start (struct usb_hcd *hcd) int ret; ohci_dbg (ohci, "ohci_lh7a404_start, ohci:%p", ohci); - - ohci->hcca = dma_alloc_coherent (hcd->self.controller, - sizeof *ohci->hcca, &ohci->hcca_dma, 0); - if (!ohci->hcca) - return -ENOMEM; - - ohci_dbg (ohci, "ohci_lh7a404_start, ohci->hcca:%p", - ohci->hcca); - - memset (ohci->hcca, 0, sizeof (struct ohci_hcca)); - - if ((ret = ohci_mem_init (ohci)) < 0) { - ohci_stop (hcd); + if ((ret = ohci_init(ohci)) < 0) return ret; - } - ohci->regs = hcd->regs; - - if (hc_reset (ohci) < 0) { - ohci_stop (hcd); - return -ENODEV; - } - if (hc_start (ohci) < 0) { + if ((ret = ohci_run (ohci)) < 0) { err ("can't start %s", ohci->hcd.self.bus_name); ohci_stop (hcd); - return -EBUSY; + return ret; } - create_debug_files (ohci); - -#ifdef DEBUG - ohci_dump (ohci, 1); -#endif /*DEBUG*/ return 0; } diff --git a/drivers/usb/host/ohci-omap.c b/drivers/usb/host/ohci-omap.c index d133ff22a4a7..a8e641f595bb 100644 --- a/drivers/usb/host/ohci-omap.c +++ b/drivers/usb/host/ohci-omap.c @@ -428,40 +428,18 @@ ohci_omap_start (struct usb_hcd *hcd) struct ohci_hcd *ohci = hcd_to_ohci (hcd); int ret; - config = hcd->self.controller->platform_data; - ohci->hcca = dma_alloc_coherent (hcd->self.controller, - sizeof *ohci->hcca, &ohci->hcca_dma, 0); - if (!ohci->hcca) - return -ENOMEM; - - memset (ohci->hcca, 0, sizeof (struct ohci_hcca)); - if ((ret = ohci_mem_init (ohci)) < 0) { - ohci_stop (hcd); + if ((ret = ohci_init(ohci)) < 0) return ret; - } - ohci->regs = hcd->regs; + config = hcd->self.controller->platform_data; if (config->otg || config->rwc) writel(OHCI_CTRL_RWC, &ohci->regs->control); - if (hc_reset (ohci) < 0) { - ohci_stop (hcd); - return -ENODEV; - } - - if (hc_start (ohci) < 0) { + if ((ret = ohci_run (ohci)) < 0) { err ("can't start %s", ohci->hcd.self.bus_name); ohci_stop (hcd); - return -EBUSY; + return ret; } - if (ohci->power_budget) - hub_set_power_budget(ohci->hcd.self.root_hub, - ohci->power_budget); - create_debug_files (ohci); - -#ifdef DEBUG - ohci_dump (ohci, 1); -#endif return 0; } diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index bf0c5d8ec231..b1dbea9e4b23 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c @@ -35,9 +35,7 @@ ohci_pci_reset (struct usb_hcd *hcd) { struct ohci_hcd *ohci = hcd_to_ohci (hcd); - ohci->regs = hcd->regs; - ohci->next_statechange = jiffies; - return hc_reset (ohci); + return ohci_init (ohci); } static int __devinit @@ -46,11 +44,6 @@ ohci_pci_start (struct usb_hcd *hcd) struct ohci_hcd *ohci = hcd_to_ohci (hcd); int ret; - ohci->hcca = dma_alloc_coherent (hcd->self.controller, - sizeof *ohci->hcca, &ohci->hcca_dma, 0); - if (!ohci->hcca) - return -ENOMEM; - if(hcd->self.controller && hcd->self.controller->bus == &pci_bus_type) { struct pci_dev *pdev = to_pci_dev(hcd->self.controller); @@ -104,31 +97,14 @@ ohci_pci_start (struct usb_hcd *hcd) } - memset (ohci->hcca, 0, sizeof (struct ohci_hcca)); - if ((ret = ohci_mem_init (ohci)) < 0) { - ohci_stop (hcd); - return ret; - } - - /* NOTE: this is a second reset. the first one helps - * keep bios/smm irqs from making trouble, but it was - * probably more than 1msec ago... + /* NOTE: there may have already been a first reset, to + * keep bios/smm irqs from making trouble */ - if (hc_reset (ohci) < 0) { - ohci_stop (hcd); - return -ENODEV; - } - - if (hc_start (ohci) < 0) { + if ((ret = ohci_run (ohci)) < 0) { ohci_err (ohci, "can't start\n"); ohci_stop (hcd); - return -EBUSY; + return ret; } - create_debug_files (ohci); - -#ifdef DEBUG - ohci_dump (ohci, 1); -#endif return 0; } diff --git a/drivers/usb/host/ohci-sa1111.c b/drivers/usb/host/ohci-sa1111.c index c3cd76aba163..35d71aceff63 100644 --- a/drivers/usb/host/ohci-sa1111.c +++ b/drivers/usb/host/ohci-sa1111.c @@ -272,33 +272,14 @@ ohci_sa1111_start (struct usb_hcd *hcd) struct ohci_hcd *ohci = hcd_to_ohci (hcd); int ret; - ohci->hcca = dma_alloc_coherent (hcd->self.controller, - sizeof *ohci->hcca, &ohci->hcca_dma, 0); - if (!ohci->hcca) - return -ENOMEM; - - memset (ohci->hcca, 0, sizeof (struct ohci_hcca)); - if ((ret = ohci_mem_init (ohci)) < 0) { - ohci_stop (hcd); + if ((ret = ohci_init(ohci)) < 0) return ret; - } - ohci->regs = hcd->regs; - - if (hc_reset (ohci) < 0) { - ohci_stop (hcd); - return -ENODEV; - } - if (hc_start (ohci) < 0) { + if ((ret = ohci_run (ohci)) < 0) { err ("can't start %s", ohci->hcd.self.bus_name); ohci_stop (hcd); - return -EBUSY; + return ret; } - create_debug_files (ohci); - -#ifdef DEBUG - ohci_dump (ohci, 1); -#endif return 0; } diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h index 079bbc1b00c9..6a3fa725ef4a 100644 --- a/drivers/usb/host/ohci.h +++ b/drivers/usb/host/ohci.h @@ -42,7 +42,6 @@ struct ed { /* create --> IDLE --> OPER --> ... --> IDLE --> destroy * usually: OPER --> UNLINK --> (IDLE | OPER) --> ... - * some special cases : OPER --> IDLE ... */ u8 state; /* ED_{IDLE,UNLINK,OPER} */ #define ED_IDLE 0x00 /* NOT linked to HC */ @@ -406,15 +405,14 @@ static inline void disable (struct ohci_hcd *ohci) } #define FI 0x2edf /* 12000 bits per frame (-1) */ -#define FSMP(fi) ((6 * ((fi) - 210)) / 7) +#define FSMP(fi) (0x7fff & ((6 * ((fi) - 210)) / 7)) #define LSTHRESH 0x628 /* lowspeed bit threshold */ static inline void periodic_reinit (struct ohci_hcd *ohci) { u32 fi = ohci->fminterval & 0x0ffff; - writel (ohci->fminterval, &ohci->regs->fminterval); + writel (((9 * fi) / 10) & 0x3fff, &ohci->regs->periodicstart); - writel (LSTHRESH, &ohci->regs->lsthresh); } /*-------------------------------------------------------------------------*/ @@ -438,6 +436,8 @@ static inline void periodic_reinit (struct ohci_hcd *ohci) # define ohci_vdbg(ohci, fmt, args...) do { } while (0) #endif +/*-------------------------------------------------------------------------*/ + #ifdef CONFIG_ARCH_LH7A404 /* Marc Singer: at the time this code was written, the LH7A404 * had a problem reading the USB host registers. This @@ -457,3 +457,25 @@ static inline unsigned int ohci_readl (void* regs) return readl (regs); } #endif + +/* AMD-756 (D2 rev) reports corrupt register contents in some cases. + * The erratum (#4) description is incorrect. AMD's workaround waits + * till some bits (mostly reserved) are clear; ok for all revs. + */ +#define read_roothub(hc, register, mask) ({ \ + u32 temp = ohci_readl (&hc->regs->roothub.register); \ + if (temp == -1) \ + disable (hc); \ + else if (hc->flags & OHCI_QUIRK_AMD756) \ + while (temp & mask) \ + temp = ohci_readl (&hc->regs->roothub.register); \ + temp; }) + +static u32 roothub_a (struct ohci_hcd *hc) + { return read_roothub (hc, a, 0xfc0fe000); } +static inline u32 roothub_b (struct ohci_hcd *hc) + { return ohci_readl (&hc->regs->roothub.b); } +static inline u32 roothub_status (struct ohci_hcd *hc) + { return ohci_readl (&hc->regs->roothub.status); } +static u32 roothub_portstatus (struct ohci_hcd *hc, int i) + { return read_roothub (hc, portstatus [i], 0xffe0fce0); } -- cgit v1.2.3 From 11c763cc105faf3f3148fa209b87ded8bb1fa07e Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Mon, 13 Sep 2004 20:33:44 -0700 Subject: [PATCH] USB: Updated USB device locking This patch reintroduces the USB device locking code we tried out earlier. As before, it solves the problem of effectively locking all the devices while drivers are registered and unregistered by introducing an rwsem. Unlike the earlier attempt, this version does not ever try to acquire a lock re-entrantly. I trust that will eliminate the races and hang-ups you observed with the earlier version. There are also copious comments explaining exactly how things should work. The patch interacts slightly with the locktree() code introduced by David for suspend/resume support. It doesn't change the functionality at all; it just updates the routine to follow the new locking rules. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/devices.c | 6 +- drivers/usb/core/devio.c | 38 ++++++------ drivers/usb/core/hcd.c | 4 +- drivers/usb/core/hub.c | 93 ++++++++++++++++------------- drivers/usb/core/message.c | 16 ++--- drivers/usb/core/sysfs.c | 4 +- drivers/usb/core/usb.c | 138 +++++++++++++++++++++++++++++++++++++++++--- drivers/usb/core/usb.h | 2 + drivers/usb/host/ehci-hub.c | 2 +- drivers/usb/host/ohci-hub.c | 10 ++-- drivers/usb/host/ohci-pci.c | 8 +-- include/linux/usb.h | 12 ++++ 12 files changed, 242 insertions(+), 91 deletions(-) diff --git a/drivers/usb/core/devices.c b/drivers/usb/core/devices.c index 195e365675af..50009ed51e8d 100644 --- a/drivers/usb/core/devices.c +++ b/drivers/usb/core/devices.c @@ -451,7 +451,7 @@ static char *usb_dump_string(char *start, char *end, const struct usb_device *de * nbytes - the maximum number of bytes to write * skip_bytes - the number of bytes to skip before writing anything * file_offset - the offset into the devices file on completion - * The caller must own the usbdev->serialize semaphore. + * The caller must own the device lock. */ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, loff_t *skip_bytes, loff_t *file_offset, struct usb_device *usbdev, struct usb_bus *bus, int level, int index, int count) @@ -586,9 +586,9 @@ static ssize_t usb_device_read(struct file *file, char __user *buf, size_t nbyte /* recurse through all children of the root hub */ if (!bus->root_hub) continue; - down(&bus->root_hub->serialize); + usb_lock_device(bus->root_hub); ret = usb_device_dump(&buf, &nbytes, &skip_bytes, ppos, bus->root_hub, bus, 0, 0, 0); - up(&bus->root_hub->serialize); + usb_unlock_device(bus->root_hub); if (ret < 0) { up(&usb_bus_list_lock); return ret; diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index 0b3bf6634ee4..c3e76e465c2b 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -113,7 +113,7 @@ static ssize_t usbdev_read(struct file *file, char __user *buf, size_t nbytes, l int i; pos = *ppos; - down(&dev->serialize); + usb_lock_device(dev); if (!connected(dev)) { ret = -ENODEV; goto err; @@ -175,7 +175,7 @@ static ssize_t usbdev_read(struct file *file, char __user *buf, size_t nbytes, l } err: - up(&dev->serialize); + usb_unlock_device(dev); return ret; } @@ -517,7 +517,7 @@ static int usbdev_release(struct inode *inode, struct file *file) struct usb_device *dev = ps->dev; unsigned int ifnum; - down(&dev->serialize); + usb_lock_device(dev); list_del_init(&ps->list); if (connected(dev)) { @@ -526,7 +526,7 @@ static int usbdev_release(struct inode *inode, struct file *file) releaseintf(ps, ifnum); destroy_all_async(ps); } - up(&dev->serialize); + usb_unlock_device(dev); usb_put_dev(dev); ps->dev = NULL; kfree(ps); @@ -558,10 +558,10 @@ static int proc_control(struct dev_state *ps, void __user *arg) snoop(&dev->dev, "control read: bRequest=%02x bRrequestType=%02x wValue=%04x wIndex=%04x\n", ctrl.bRequest, ctrl.bRequestType, ctrl.wValue, ctrl.wIndex); - up(&dev->serialize); + usb_unlock_device(dev); i = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), ctrl.bRequest, ctrl.bRequestType, ctrl.wValue, ctrl.wIndex, tbuf, ctrl.wLength, tmo); - down(&dev->serialize); + usb_lock_device(dev); if ((i > 0) && ctrl.wLength) { if (usbfs_snoop) { dev_info(&dev->dev, "control read: data "); @@ -589,10 +589,10 @@ static int proc_control(struct dev_state *ps, void __user *arg) printk ("%02x ", (unsigned char)(tbuf)[j]); printk("\n"); } - up(&dev->serialize); + usb_unlock_device(dev); i = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), ctrl.bRequest, ctrl.bRequestType, ctrl.wValue, ctrl.wIndex, tbuf, ctrl.wLength, tmo); - down(&dev->serialize); + usb_lock_device(dev); } free_page((unsigned long)tbuf); if (i<0) { @@ -636,9 +636,9 @@ static int proc_bulk(struct dev_state *ps, void __user *arg) kfree(tbuf); return -EINVAL; } - up(&dev->serialize); + usb_unlock_device(dev); i = usb_bulk_msg(dev, pipe, tbuf, len1, &len2, tmo); - down(&dev->serialize); + usb_lock_device(dev); if (!i && len2) { if (copy_to_user(bulk.data, tbuf, len2)) { kfree(tbuf); @@ -652,9 +652,9 @@ static int proc_bulk(struct dev_state *ps, void __user *arg) return -EFAULT; } } - up(&dev->serialize); + usb_unlock_device(dev); i = usb_bulk_msg(dev, pipe, tbuf, len1, &len2, tmo); - down(&dev->serialize); + usb_lock_device(dev); } kfree(tbuf); if (i < 0) { @@ -1025,9 +1025,9 @@ static int proc_reapurb(struct dev_state *ps, void __user *arg) break; if (signal_pending(current)) break; - up(&dev->serialize); + usb_unlock_device(dev); schedule(); - down(&dev->serialize); + usb_lock_device(dev); } remove_wait_queue(&ps->wait, &wait); set_current_state(TASK_RUNNING); @@ -1150,7 +1150,11 @@ static int proc_ioctl (struct dev_state *ps, void __user *arg) /* let kernel drivers try to (re)bind to the interface */ case USBDEVFS_CONNECT: + usb_unlock_device(ps->dev); + usb_lock_all_devices(); bus_rescan_devices(intf->dev.bus); + usb_unlock_all_devices(); + usb_lock_device(ps->dev); break; /* talk directly to the interface's driver */ @@ -1193,9 +1197,9 @@ static int usbdev_ioctl(struct inode *inode, struct file *file, unsigned int cmd if (!(file->f_mode & FMODE_WRITE)) return -EPERM; - down(&dev->serialize); + usb_lock_device(dev); if (!connected(dev)) { - up(&dev->serialize); + usb_unlock_device(dev); return -ENODEV; } @@ -1295,7 +1299,7 @@ static int usbdev_ioctl(struct inode *inode, struct file *file, unsigned int cmd ret = proc_ioctl(ps, p); break; } - up(&dev->serialize); + usb_unlock_device(dev); if (ret >= 0) inode->i_atime = CURRENT_TIME; return ret; diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index c1de465a10d1..ed3e723cc9c5 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -797,9 +797,9 @@ int usb_register_root_hub (struct usb_device *usb_dev, struct device *parent_dev return (retval < 0) ? retval : -EMSGSIZE; } - down (&usb_dev->serialize); + usb_lock_device (usb_dev); retval = usb_new_device (usb_dev); - up (&usb_dev->serialize); + usb_unlock_device (usb_dev); if (retval) { usb_dev->bus->root_hub = NULL; dev_err (parent_dev, "can't register root hub for %s, %d\n", diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 29bba5e78da3..36ef3db882a2 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -36,7 +36,9 @@ #include "hcd.h" #include "hub.h" -/* Protect struct usb_device state and children members */ +/* Protect struct usb_device->state and ->children members + * Note: Both are also protected by ->serialize, except that ->state can + * change to USB_STATE_NOTATTACHED even when the semaphore isn't held. */ static spinlock_t device_state_lock = SPIN_LOCK_UNLOCKED; /* khubd's worklist and its lock */ @@ -857,17 +859,6 @@ static void hub_start_disconnect(struct usb_device *hdev) } -static void recursively_mark_NOTATTACHED(struct usb_device *udev) -{ - int i; - - for (i = 0; i < udev->maxchild; ++i) { - if (udev->children[i]) - recursively_mark_NOTATTACHED(udev->children[i]); - } - udev->state = USB_STATE_NOTATTACHED; -} - /* grab device/port lock, returning index of that port (zero based). * protects the upstream link used by this device from concurrent * tree operations like suspend, resume, reset, and disconnect, which @@ -884,21 +875,16 @@ static int locktree(struct usb_device *udev) /* root hub is always the first lock in the series */ hdev = udev->parent; if (!hdev) { - down(&udev->serialize); + usb_lock_device(udev); return 0; } /* on the path from root to us, lock everything from * top down, dropping parent locks when not needed - * - * NOTE: if disconnect were to ignore the locking, we'd need - * to get extra refcounts to everything since hdev->children - * and udev->parent could be invalidated while we work... */ t = locktree(hdev); if (t < 0) return t; - spin_lock_irq(&device_state_lock); for (t = 0; t < hdev->maxchild; t++) { if (hdev->children[t] == udev) { /* everything is fail-fast once disconnect @@ -910,33 +896,45 @@ static int locktree(struct usb_device *udev) /* when everyone grabs locks top->bottom, * non-overlapping work may be concurrent */ - spin_unlock_irq(&device_state_lock); down(&udev->serialize); up(&hdev->serialize); return t; } } - spin_unlock_irq(&device_state_lock); - up(&hdev->serialize); + usb_unlock_device(hdev); return -ENODEV; } +static void recursively_mark_NOTATTACHED(struct usb_device *udev) +{ + int i; + + for (i = 0; i < udev->maxchild; ++i) { + if (udev->children[i]) + recursively_mark_NOTATTACHED(udev->children[i]); + } + udev->state = USB_STATE_NOTATTACHED; +} + /** * usb_set_device_state - change a device's current state (usbcore, hcds) * @udev: pointer to device whose state should be changed * @new_state: new state value to be stored * - * udev->state is _not_ protected by the device lock. This + * udev->state is _not_ fully protected by the device lock. Although + * most transitions are made only while holding the lock, the state can + * can change to USB_STATE_NOTATTACHED at almost any time. This * is so that devices can be marked as disconnected as soon as possible, - * without having to wait for the semaphore to be released. Instead, - * changes to the state must be protected by the device_state_lock spinlock. + * without having to wait for any semaphores to be released. As a result, + * all changes to any device's state must be protected by the + * device_state_lock spinlock. * * Once a device has been added to the device tree, all changes to its state * should be made using this routine. The state should _not_ be set directly. * * If udev->state is already USB_STATE_NOTATTACHED then no change is made. * Otherwise udev->state is set to new_state, and if new_state is - * USB_STATE_NOTATTACHED then all of udev's descendant's states are also set + * USB_STATE_NOTATTACHED then all of udev's descendants' states are also set * to USB_STATE_NOTATTACHED. */ void usb_set_device_state(struct usb_device *udev, @@ -987,11 +985,12 @@ static void release_address(struct usb_device *udev) /** * usb_disconnect - disconnect a device (usbcore-internal) - * @pdev: pointer to device being disconnected, into a locked hub + * @pdev: pointer to device being disconnected * Context: !in_interrupt () * - * Something got disconnected. Get rid of it, and all of its children. - * If *pdev is a normal device then the parent hub should be locked. + * Something got disconnected. Get rid of it and all of its children. + * + * If *pdev is a normal device then the parent hub must already be locked. * If *pdev is a root hub then this routine will acquire the * usb_bus_list_lock on behalf of the caller. * @@ -1017,9 +1016,11 @@ void usb_disconnect(struct usb_device **pdev) usb_set_device_state(udev, USB_STATE_NOTATTACHED); /* lock the bus list on behalf of HCDs unregistering their root hubs */ - if (!udev->parent) + if (!udev->parent) { down(&usb_bus_list_lock); - down(&udev->serialize); + usb_lock_device(udev); + } else + down(&udev->serialize); dev_info (&udev->dev, "USB disconnect, address %d\n", udev->devnum); @@ -1044,14 +1045,16 @@ void usb_disconnect(struct usb_device **pdev) usbfs_remove_device(udev); usb_remove_sysfs_dev_files(udev); - /* Avoid races with recursively_mark_NOTATTACHED() and locktree() */ + /* Avoid races with recursively_mark_NOTATTACHED() */ spin_lock_irq(&device_state_lock); *pdev = NULL; spin_unlock_irq(&device_state_lock); - up(&udev->serialize); - if (!udev->parent) + if (!udev->parent) { + usb_unlock_device(udev); up(&usb_bus_list_lock); + } else + up(&udev->serialize); device_unregister(&udev->dev); } @@ -1516,16 +1519,14 @@ static int __usb_suspend_device (struct usb_device *udev, int port, u32 state) { int status; + /* caller owns the udev device lock */ if (port < 0) return port; - /* NOTE: udev->serialize released on all real returns! */ - if (state <= udev->dev.power.power_state || state < PM_SUSPEND_MEM || udev->state == USB_STATE_SUSPENDED || udev->state == USB_STATE_NOTATTACHED) { - up(&udev->serialize); return 0; } @@ -1605,7 +1606,6 @@ static int __usb_suspend_device (struct usb_device *udev, int port, u32 state) if (status == 0) udev->dev.power.power_state = state; - up(&udev->serialize); return status; } @@ -1629,7 +1629,15 @@ static int __usb_suspend_device (struct usb_device *udev, int port, u32 state) */ int usb_suspend_device(struct usb_device *udev, u32 state) { - return __usb_suspend_device(udev, locktree(udev), state); + int port, status; + + port = locktree(udev); + if (port < 0) + return port; + + status = __usb_suspend_device(udev, port, state); + usb_unlock_device(udev); + return status; } /* @@ -1642,7 +1650,7 @@ static int finish_port_resume(struct usb_device *udev) int status; u16 devstatus; - /* caller owns udev->serialize */ + /* caller owns the udev device lock */ dev_dbg(&udev->dev, "usb resume\n"); udev->dev.power.power_state = PM_SUSPEND_ON; @@ -1822,10 +1830,12 @@ int usb_resume_device(struct usb_device *udev) status); } - up(&udev->serialize); + usb_unlock_device(udev); /* rebind drivers that had no suspend() */ + usb_lock_all_devices(); bus_rescan_devices(&usb_bus_type); + usb_unlock_all_devices(); return status; } @@ -1867,6 +1877,7 @@ static int hub_suspend(struct usb_interface *intf, u32 state) continue; down(&udev->serialize); status = __usb_suspend_device(udev, port, state); + up(&udev->serialize); if (status < 0) dev_dbg(&intf->dev, "suspend port %d --> %d\n", port, status); @@ -2595,7 +2606,7 @@ static void hub_events(void) } loop: - up(&hdev->serialize); + usb_unlock_device(hdev); usb_put_dev(hdev); } /* end while (1) */ diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 98695c68d687..e20dde5d793a 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -1132,6 +1132,8 @@ int usb_set_interface(struct usb_device *dev, int interface, int alternate) * use usb_set_interface() on the interfaces it claims. Resetting the whole * configuration would affect other drivers' interfaces. * + * The caller must own the device lock. + * * Returns zero on success, else a negative error code. */ int usb_reset_configuration(struct usb_device *dev) @@ -1142,9 +1144,9 @@ int usb_reset_configuration(struct usb_device *dev) if (dev->state == USB_STATE_SUSPENDED) return -EHOSTUNREACH; - /* caller must own dev->serialize (config won't change) - * and the usb bus readlock (so driver bindings are stable); - * so calls during probe() are fine + /* caller must have locked the device and must own + * the usb bus readlock (so driver bindings are stable); + * calls during probe() are fine */ for (i = 1; i < 16; ++i) { @@ -1199,7 +1201,7 @@ static void release_interface(struct device *dev) * usb_set_configuration - Makes a particular device setting be current * @dev: the device whose configuration is being updated * @configuration: the configuration being chosen. - * Context: !in_interrupt(), caller holds dev->serialize + * Context: !in_interrupt(), caller owns the device lock * * This is used to enable non-default device modes. Not all devices * use this kind of configurability; many devices only have one @@ -1220,8 +1222,8 @@ static void release_interface(struct device *dev) * usb_set_interface(). * * This call is synchronous. The calling context must be able to sleep, - * and must not hold the driver model lock for USB; usb device driver - * probe() methods may not use this routine. + * must own the device lock, and must not hold the driver model's USB + * bus rwsem; usb device driver probe() methods cannot use this routine. * * Returns zero on success, or else the status code returned by the * underlying call that failed. On succesful completion, each interface @@ -1236,8 +1238,6 @@ int usb_set_configuration(struct usb_device *dev, int configuration) struct usb_interface **new_interfaces = NULL; int n, nintf; - /* dev->serialize guards all config changes */ - for (i = 0; i < dev->descriptor.bNumConfigurations; i++) { if (dev->config[i].desc.bConfigurationValue == configuration) { cp = &dev->config[i]; diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index 78c5ca2f1051..007baee22013 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -55,9 +55,9 @@ set_bConfigurationValue (struct device *dev, const char *buf, size_t count) if (sscanf (buf, "%u", &config) != 1 || config > 255) return -EINVAL; - down(&udev->serialize); + usb_lock_device(udev); value = usb_set_configuration (udev, config); - up(&udev->serialize); + usb_unlock_device(udev); return (value < 0) ? value : count; } diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index de2edfb08582..93e2000043e4 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -39,6 +39,7 @@ #include #include #include +#include #include #include @@ -62,6 +63,8 @@ const char *usbcore_name = "usbcore"; int nousb; /* Disable USB when built into kernel image */ /* Not honored on modular build */ +static DECLARE_RWSEM(usb_all_devices_rwsem); + static int generic_probe (struct device *dev) { @@ -151,7 +154,9 @@ int usb_register(struct usb_driver *new_driver) new_driver->driver.probe = usb_probe_interface; new_driver->driver.remove = usb_unbind_interface; + usb_lock_all_devices(); retval = driver_register(&new_driver->driver); + usb_unlock_all_devices(); if (!retval) { pr_info("%s: registered new driver %s\n", @@ -180,7 +185,9 @@ void usb_deregister(struct usb_driver *driver) { pr_info("%s: deregistering driver %s\n", usbcore_name, driver->name); + usb_lock_all_devices(); driver_unregister (&driver->driver); + usb_unlock_all_devices(); usbfs_update_special(); } @@ -202,7 +209,7 @@ void usb_deregister(struct usb_driver *driver) * alternate settings available for this interfaces. * * Don't call this function unless you are bound to one of the interfaces - * on this device or you own the dev->serialize semaphore! + * on this device or you have locked the device! */ struct usb_interface *usb_ifnum_to_if(struct usb_device *dev, unsigned ifnum) { @@ -235,7 +242,7 @@ struct usb_interface *usb_ifnum_to_if(struct usb_device *dev, unsigned ifnum) * drivers avoid such mistakes. * * Don't call this function unless you are bound to the intf interface - * or you own the device's ->serialize semaphore! + * or you have locked the device! */ struct usb_host_interface *usb_altnum_to_altsetting(struct usb_interface *intf, unsigned int altnum) @@ -303,11 +310,12 @@ usb_epnum_to_ep_desc(struct usb_device *dev, unsigned epnum) * way to bind to an interface is to return the private data from * the driver's probe() method. * - * Callers must own the driver model's usb bus writelock. So driver - * probe() entries don't need extra locking, but other call contexts - * may need to explicitly claim that lock. + * Callers must own the device lock and the driver model's usb_bus_type.subsys + * writelock. So driver probe() entries don't need extra locking, + * but other call contexts may need to explicitly claim those locks. */ -int usb_driver_claim_interface(struct usb_driver *driver, struct usb_interface *iface, void* priv) +int usb_driver_claim_interface(struct usb_driver *driver, + struct usb_interface *iface, void* priv) { struct device *dev = &iface->dev; @@ -336,8 +344,8 @@ int usb_driver_claim_interface(struct usb_driver *driver, struct usb_interface * * also causes the driver disconnect() method to be called. * * This call is synchronous, and may not be used in an interrupt context. - * Callers must own the usb_device serialize semaphore and the driver model's - * usb bus writelock. So driver disconnect() entries don't need extra locking, + * Callers must own the device lock and the driver model's usb_bus_type.subsys + * writelock. So driver disconnect() entries don't need extra locking, * but other call contexts may need to explicitly claim those locks. */ void usb_driver_release_interface(struct usb_driver *driver, @@ -830,6 +838,112 @@ void usb_put_intf(struct usb_interface *intf) put_device(&intf->dev); } + +/* USB device locking + * + * Although locking USB devices should be straightforward, it is + * complicated by the way the driver-model core works. When a new USB + * driver is registered or unregistered, the core will automatically + * probe or disconnect all matching interfaces on all USB devices while + * holding the USB subsystem writelock. There's no good way for us to + * tell which devices will be used or to lock them beforehand; our only + * option is to effectively lock all the USB devices. + * + * We do that by using a private rw-semaphore, usb_all_devices_rwsem. + * When locking an individual device you must first acquire the rwsem's + * readlock. When a driver is registered or unregistered the writelock + * must be held. These actions are encapsulated in the subroutines + * below, so all a driver needs to do is call usb_lock_device() and + * usb_unlock_device(). + * + * Complications arise when several devices are to be locked at the same + * time. Only hub-aware drivers that are part of usbcore ever have to + * do this; nobody else needs to worry about it. The problem is that + * usb_lock_device() must not be called to lock a second device since it + * would acquire the rwsem's readlock reentrantly, leading to deadlock if + * another thread was waiting for the writelock. The solution is simple: + * + * When locking more than one device, call usb_lock_device() + * to lock the first one. Lock the others by calling + * down(&udev->serialize) directly. + * + * When unlocking multiple devices, use up(&udev->serialize) + * to unlock all but the last one. Unlock the last one by + * calling usb_unlock_device(). + * + * When locking both a device and its parent, always lock the + * the parent first. + */ + +/** + * usb_lock_device - acquire the lock for a usb device structure + * @udev: device that's being locked + * + * Use this routine when you don't hold any other device locks; + * to acquire nested inner locks call down(&udev->serialize) directly. + * This is necessary for proper interaction with usb_lock_all_devices(). + */ +void usb_lock_device(struct usb_device *udev) +{ + down_read(&usb_all_devices_rwsem); + down(&udev->serialize); +} + +/** + * usb_trylock_device - attempt to acquire the lock for a usb device structure + * @udev: device that's being locked + * + * Don't use this routine if you already hold a device lock; + * use down_trylock(&udev->serialize) instead. + * This is necessary for proper interaction with usb_lock_all_devices(). + * + * Returns 1 if successful, 0 if contention. + */ +int usb_trylock_device(struct usb_device *udev) +{ + if (!down_read_trylock(&usb_all_devices_rwsem)) + return 0; + if (down_trylock(&udev->serialize)) { + up_read(&usb_all_devices_rwsem); + return 0; + } + return 1; +} + +/** + * usb_unlock_device - release the lock for a usb device structure + * @udev: device that's being unlocked + * + * Use this routine when releasing the only device lock you hold; + * to release inner nested locks call up(&udev->serialize) directly. + * This is necessary for proper interaction with usb_lock_all_devices(). + */ +void usb_unlock_device(struct usb_device *udev) +{ + up(&udev->serialize); + up_read(&usb_all_devices_rwsem); +} + +/** + * usb_lock_all_devices - acquire the lock for all usb device structures + * + * This is necessary when registering a new driver or probing a bus, + * since the driver-model core may try to use any usb_device. + */ +void usb_lock_all_devices(void) +{ + down_write(&usb_all_devices_rwsem); +} + +/** + * usb_unlock_all_devices - release the lock for all usb device structures + */ +void usb_unlock_all_devices(void) +{ + up_write(&usb_all_devices_rwsem); +} + + static struct usb_device *match_device(struct usb_device *dev, u16 vendor_id, u16 product_id) { @@ -851,8 +965,10 @@ static struct usb_device *match_device(struct usb_device *dev, /* look through all of the children of this device */ for (child = 0; child < dev->maxchild; ++child) { if (dev->children[child]) { + down(&dev->children[child]->serialize); ret_dev = match_device(dev->children[child], vendor_id, product_id); + up(&dev->children[child]->serialize); if (ret_dev) goto exit; } @@ -887,7 +1003,9 @@ struct usb_device *usb_find_device(u16 vendor_id, u16 product_id) bus = container_of(buslist, struct usb_bus, bus_list); if (!bus->root_hub) continue; + usb_lock_device(bus->root_hub); dev = match_device(bus->root_hub, vendor_id, product_id); + usb_unlock_device(bus->root_hub); if (dev) goto exit; } @@ -1373,6 +1491,10 @@ EXPORT_SYMBOL(usb_put_dev); EXPORT_SYMBOL(usb_get_dev); EXPORT_SYMBOL(usb_hub_tt_clear_buffer); +EXPORT_SYMBOL(usb_lock_device); +EXPORT_SYMBOL(usb_trylock_device); +EXPORT_SYMBOL(usb_unlock_device); + EXPORT_SYMBOL(usb_driver_claim_interface); EXPORT_SYMBOL(usb_driver_release_interface); EXPORT_SYMBOL(usb_match_id); diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index 8e9c923bff0b..30d2cf7dd762 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -22,6 +22,8 @@ extern int usb_get_device_descriptor(struct usb_device *dev, unsigned int size); extern int usb_set_configuration(struct usb_device *dev, int configuration); +extern void usb_lock_all_devices(void); +extern void usb_unlock_all_devices(void); /* for labeling diagnostics */ extern const char *usbcore_name; diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 438701e17946..4a23fbf1f7af 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -81,7 +81,7 @@ static int ehci_hub_suspend (struct usb_hcd *hcd) } -/* caller owns root->serialize, and should reset/reinit on error */ +/* caller has locked the root hub, and should reset/reinit on error */ static int ehci_hub_resume (struct usb_hcd *hcd) { struct ehci_hcd *ehci = hcd_to_ehci (hcd); diff --git a/drivers/usb/host/ohci-hub.c b/drivers/usb/host/ohci-hub.c index 1f21cf815ad4..d7a28d8eefb9 100644 --- a/drivers/usb/host/ohci-hub.c +++ b/drivers/usb/host/ohci-hub.c @@ -138,7 +138,7 @@ static inline struct ed *find_head (struct ed *ed) return ed; } -/* caller owns root->serialize */ +/* caller has locked the root hub */ static int ohci_hub_resume (struct usb_hcd *hcd) { struct ohci_hcd *ohci = hcd_to_ohci (hcd); @@ -282,9 +282,9 @@ static void ohci_rh_resume (void *_hcd) { struct usb_hcd *hcd = _hcd; - down (&hcd->self.root_hub->serialize); + usb_lock_device (hcd->self.root_hub); (void) ohci_hub_resume (hcd); - up (&hcd->self.root_hub->serialize); + usb_unlock_device (hcd->self.root_hub); } #else @@ -362,12 +362,12 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf) && ((OHCI_CTRL_HCFS | OHCI_SCHED_ENABLES) & ohci->hc_control) == OHCI_USB_OPER - && down_trylock (&hcd->self.root_hub->serialize) == 0 + && usb_trylock_device (hcd->self.root_hub) ) { ohci_vdbg (ohci, "autosuspend\n"); (void) ohci_hub_suspend (&ohci->hcd); ohci->hcd.state = USB_STATE_RUNNING; - up (&hcd->self.root_hub->serialize); + usb_unlock_device (hcd->self.root_hub); } #endif diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index b1dbea9e4b23..23f6b5f3fee5 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c @@ -121,9 +121,9 @@ static int ohci_pci_suspend (struct usb_hcd *hcd, u32 state) #ifdef CONFIG_USB_SUSPEND (void) usb_suspend_device (hcd->self.root_hub, state); #else - down (&hcd->self.root_hub->serialize); + usb_lock_device (hcd->self.root_hub); (void) ohci_hub_suspend (hcd); - up (&hcd->self.root_hub->serialize); + usb_unlock_device (hcd->self.root_hub); #endif /* let things settle down a bit */ @@ -169,9 +169,9 @@ static int ohci_pci_resume (struct usb_hcd *hcd) /* get extra cleanup even if remote wakeup isn't in use */ retval = usb_resume_device (hcd->self.root_hub); #else - down (&hcd->self.root_hub->serialize); + usb_lock_device (hcd->self.root_hub); retval = ohci_hub_resume (hcd); - up (&hcd->self.root_hub->serialize); + usb_unlock_device (hcd->self.root_hub); #endif if (retval == 0) { diff --git a/include/linux/usb.h b/include/linux/usb.h index d07d5aba9e7f..67a8d7bce43b 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -281,6 +281,14 @@ struct usb_bus { struct usb_tt; +/* + * struct usb_device - kernel's representation of a USB device + * + * FIXME: Write the kerneldoc! + * + * Usbcore drivers should not set usbdev->state directly. Instead use + * usb_set_device_state(). + */ struct usb_device { int devnum; /* Address on USB bus */ char devpath [16]; /* Use in messages: /port/port/... */ @@ -331,6 +339,10 @@ struct usb_device { extern struct usb_device *usb_get_dev(struct usb_device *dev); extern void usb_put_dev(struct usb_device *dev); +extern void usb_lock_device(struct usb_device *udev); +extern int usb_trylock_device(struct usb_device *udev); +extern void usb_unlock_device(struct usb_device *udev); + /* mostly for devices emulating SCSI over USB */ extern int usb_reset_device(struct usb_device *dev); extern int __usb_reset_device(struct usb_device *dev); -- cgit v1.2.3 From 7d8d0405f07a1777cad6898346127b478b68dd0e Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Mon, 13 Sep 2004 20:34:46 -0700 Subject: [PATCH] USB: Add locking support for USB device resets This patch reintroduces the usb_lock_device_for_reset() routine, which is specially tailored to meet the needs of drivers that have to reset a device either during probe() or during normal operations. It updates a few drivers that do device resets, to make them use the new routine. It also adds a new field to struct usb_interface, to keep track of whether the interface is in the process of being bound to or unbound from a driver. This is necessary, because during binding we know the device is already locked so we don't want to try to acquire the lock again! With this patch in place, USB device resets should finally become reliable. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/net/irda/stir4200.c | 10 ++++++++ drivers/usb/core/devio.c | 2 +- drivers/usb/core/hub.c | 23 +++++++---------- drivers/usb/core/usb.c | 57 ++++++++++++++++++++++++++++++++++++++++++ drivers/usb/image/microtek.c | 11 ++++++-- drivers/usb/image/microtek.h | 1 + drivers/usb/storage/scsiglue.c | 14 ++++++++--- include/linux/usb.h | 15 +++++++++-- 8 files changed, 111 insertions(+), 22 deletions(-) diff --git a/drivers/net/irda/stir4200.c b/drivers/net/irda/stir4200.c index 81282d94f91d..5f26d9ff30e9 100644 --- a/drivers/net/irda/stir4200.c +++ b/drivers/net/irda/stir4200.c @@ -168,6 +168,7 @@ enum StirTestMask { struct stir_cb { struct usb_device *usbdev; /* init: probe_irda */ + struct usb_interface *usbintf; struct net_device *netdev; /* network layer */ struct irlap_cb *irlap; /* The link layer we are binded to */ struct net_device_stats stats; /* network statistics */ @@ -508,6 +509,7 @@ static int change_speed(struct stir_cb *stir, unsigned speed) { int i, err; __u8 mode; + int rc; for (i = 0; i < ARRAY_SIZE(stir_modes); ++i) { if (speed == stir_modes[i].speed) @@ -521,7 +523,14 @@ static int change_speed(struct stir_cb *stir, unsigned speed) pr_debug("speed change from %d to %d\n", stir->speed, speed); /* sometimes needed to get chip out of stuck state */ + rc = usb_lock_device_for_reset(stir->usbdev, stir->usbintf); + if (rc < 0) { + err = rc; + goto out; + } err = usb_reset_device(stir->usbdev); + if (rc) + usb_unlock_device(stir->usbdev); if (err) goto out; @@ -1066,6 +1075,7 @@ static int stir_probe(struct usb_interface *intf, stir = net->priv; stir->netdev = net; stir->usbdev = dev; + stir->usbintf = intf; ret = usb_reset_configuration(dev); if (ret != 0) { diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index c3e76e465c2b..0e0ea0806606 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -735,7 +735,7 @@ static int proc_connectinfo(struct dev_state *ps, void __user *arg) static int proc_resetdevice(struct dev_state *ps) { - return __usb_reset_device(ps->dev); + return usb_reset_device(ps->dev); } diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 36ef3db882a2..848ab62e71a6 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -826,7 +826,7 @@ static int hub_reset(struct usb_hub *hub) else return -1; - if (__usb_reset_device(hdev)) + if (usb_reset_device(hdev)) return -1; hub->urb->dev = hdev; @@ -2759,8 +2759,10 @@ static int config_descriptors_changed(struct usb_device *udev) * * The caller must own the device lock. For example, it's safe to use * this from a driver probe() routine after downloading new firmware. + * For calls that might not occur during probe(), drivers should lock + * the device using usb_lock_device_for_reset(). */ -int __usb_reset_device(struct usb_device *udev) +int usb_reset_device(struct usb_device *udev) { struct usb_device *parent = udev->parent; struct usb_device_descriptor descriptor = udev->descriptor; @@ -2795,6 +2797,11 @@ int __usb_reset_device(struct usb_device *udev) return -ENOENT; } + /* ep0 maxpacket size may change; let the HCD know about it. + * Other endpoints will be handled by re-enumeration. */ + usb_disable_endpoint(udev, 0); + usb_disable_endpoint(udev, 0 + USB_DIR_IN); + ret = hub_port_init(parent, udev, port); if (ret < 0) goto re_enumerate; @@ -2848,15 +2855,3 @@ re_enumerate: hub_port_logical_disconnect(parent, port); return -ENODEV; } -EXPORT_SYMBOL(__usb_reset_device); - -int usb_reset_device(struct usb_device *udev) -{ - int r; - - down(&udev->serialize); - r = __usb_reset_device(udev); - up(&udev->serialize); - - return r; -} diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 93e2000043e4..c4c457975ae3 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -102,7 +102,10 @@ int usb_probe_interface(struct device *dev) id = usb_match_id (intf, driver->id_table); if (id) { dev_dbg (dev, "%s - got id\n", __FUNCTION__); + intf->condition = USB_INTERFACE_BINDING; error = driver->probe (intf, id); + intf->condition = error ? USB_INTERFACE_UNBOUND : + USB_INTERFACE_BOUND; } return error; @@ -114,6 +117,8 @@ int usb_unbind_interface(struct device *dev) struct usb_interface *intf = to_usb_interface(dev); struct usb_driver *driver = to_usb_driver(intf->dev.driver); + intf->condition = USB_INTERFACE_UNBINDING; + /* release all urbs for this interface */ usb_disable_interface(interface_to_usbdev(intf), intf); @@ -125,6 +130,7 @@ int usb_unbind_interface(struct device *dev) intf->altsetting[0].desc.bInterfaceNumber, 0); usb_set_intfdata(intf, NULL); + intf->condition = USB_INTERFACE_UNBOUND; return 0; } @@ -324,6 +330,7 @@ int usb_driver_claim_interface(struct usb_driver *driver, dev->driver = &driver->driver; usb_set_intfdata(iface, priv); + iface->condition = USB_INTERFACE_BOUND; /* if interface was already added, bind now; else let * the future device_add() bind it, bypassing probe() @@ -363,6 +370,7 @@ void usb_driver_release_interface(struct usb_driver *driver, dev->driver = NULL; usb_set_intfdata(iface, NULL); + iface->condition = USB_INTERFACE_UNBOUND; } /** @@ -910,6 +918,54 @@ int usb_trylock_device(struct usb_device *udev) return 1; } +/** + * usb_lock_device_for_reset - cautiously acquire the lock for a + * usb device structure + * @udev: device that's being locked + * @iface: interface bound to the driver making the request (optional) + * + * Attempts to acquire the device lock, but fails if the device is + * NOTATTACHED or SUSPENDED, or if iface is specified and the interface + * is neither BINDING nor BOUND. Rather than sleeping to wait for the + * lock, the routine polls repeatedly. This is to prevent deadlock with + * disconnect; in some drivers (such as usb-storage) the disconnect() + * callback will block waiting for a device reset to complete. + * + * Returns a negative error code for failure, otherwise 1 or 0 to indicate + * that the device will or will not have to be unlocked. (0 can be + * returned when an interface is given and is BINDING, because in that + * case the driver already owns the device lock.) + */ +int usb_lock_device_for_reset(struct usb_device *udev, + struct usb_interface *iface) +{ + if (udev->state == USB_STATE_NOTATTACHED) + return -ENODEV; + if (udev->state == USB_STATE_SUSPENDED) + return -EHOSTUNREACH; + if (iface) { + switch (iface->condition) { + case USB_INTERFACE_BINDING: + return 0; + case USB_INTERFACE_BOUND: + break; + default: + return -EINTR; + } + } + + while (!usb_trylock_device(udev)) { + msleep(15); + if (udev->state == USB_STATE_NOTATTACHED) + return -ENODEV; + if (udev->state == USB_STATE_SUSPENDED) + return -EHOSTUNREACH; + if (iface && iface->condition != USB_INTERFACE_BOUND) + return -EINTR; + } + return 1; +} + /** * usb_unlock_device - release the lock for a usb device structure * @udev: device that's being unlocked @@ -1493,6 +1549,7 @@ EXPORT_SYMBOL(usb_hub_tt_clear_buffer); EXPORT_SYMBOL(usb_lock_device); EXPORT_SYMBOL(usb_trylock_device); +EXPORT_SYMBOL(usb_lock_device_for_reset); EXPORT_SYMBOL(usb_unlock_device); EXPORT_SYMBOL(usb_driver_claim_interface); diff --git a/drivers/usb/image/microtek.c b/drivers/usb/image/microtek.c index 14b218239a8c..2a18c35629eb 100644 --- a/drivers/usb/image/microtek.c +++ b/drivers/usb/image/microtek.c @@ -341,12 +341,18 @@ static int mts_scsi_abort (Scsi_Cmnd *srb) static int mts_scsi_host_reset (Scsi_Cmnd *srb) { struct mts_desc* desc = (struct mts_desc*)(srb->device->host->hostdata[0]); + int result, rc; MTS_DEBUG_GOT_HERE(); mts_debug_dump(desc); - usb_reset_device(desc->usb_dev); /*FIXME: untested on new reset code */ - return 0; /* RANT why here 0 and not SUCCESS */ + rc = usb_lock_device_for_reset(desc->usb_dev, desc->usb_intf); + if (rc < 0) + return FAILED; + result = usb_reset_device(desc->usb_dev);; + if (rc) + usb_unlock_device(desc->usb_dev); + return result ? FAILED : SUCCESS; } static @@ -777,6 +783,7 @@ static int mts_usb_probe(struct usb_interface *intf, goto out_kfree; new_desc->usb_dev = dev; + new_desc->usb_intf = intf; init_MUTEX(&new_desc->lock); /* endpoints */ diff --git a/drivers/usb/image/microtek.h b/drivers/usb/image/microtek.h index 206994ddcedf..3271deb8c001 100644 --- a/drivers/usb/image/microtek.h +++ b/drivers/usb/image/microtek.h @@ -31,6 +31,7 @@ struct mts_desc { struct mts_desc *prev; struct usb_device *usb_dev; + struct usb_interface *usb_intf; /* Endpoint addresses */ u8 ep_out; diff --git a/drivers/usb/storage/scsiglue.c b/drivers/usb/storage/scsiglue.c index 29f4e59759e6..83d7003861ff 100644 --- a/drivers/usb/storage/scsiglue.c +++ b/drivers/usb/storage/scsiglue.c @@ -283,7 +283,7 @@ static int device_reset(struct scsi_cmnd *srb) static int bus_reset(struct scsi_cmnd *srb) { struct us_data *us = (struct us_data *)srb->device->host->hostdata[0]; - int result; + int result, rc; US_DEBUGP("%s called\n", __FUNCTION__); if (us->sm_state != US_STATE_IDLE) { @@ -308,8 +308,16 @@ static int bus_reset(struct scsi_cmnd *srb) result = -EBUSY; US_DEBUGP("Refusing to reset a multi-interface device\n"); } else { - result = usb_reset_device(us->pusb_dev); - US_DEBUGP("usb_reset_device returns %d\n", result); + rc = usb_lock_device_for_reset(us->pusb_dev, us->pusb_intf); + if (rc < 0) { + US_DEBUGP("unable to lock device for reset: %d\n", rc); + result = rc; + } else { + result = usb_reset_device(us->pusb_dev); + if (rc) + usb_unlock_device(us->pusb_dev); + US_DEBUGP("usb_reset_device returns %d\n", result); + } } up(&(us->dev_semaphore)); diff --git a/include/linux/usb.h b/include/linux/usb.h index 67a8d7bce43b..18ee0751a32b 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -61,6 +61,13 @@ struct usb_host_interface { int extralen; }; +enum usb_interface_condition { + USB_INTERFACE_UNBOUND = 0, + USB_INTERFACE_BINDING, + USB_INTERFACE_BOUND, + USB_INTERFACE_UNBINDING, +}; + /** * struct usb_interface - what usb device drivers talk to * @altsetting: array of interface structures, one for each alternate @@ -75,6 +82,8 @@ struct usb_host_interface { * be unused. The driver should set this value in the probe() * function of the driver, after it has been assigned a minor * number from the USB core by calling usb_register_dev(). + * @condition: binding state of the interface: not bound, binding + * (in probe()), bound to a driver, or unbinding (in disconnect()) * @dev: driver model's view of this device * @class_dev: driver model's class view of this device. * @@ -113,6 +122,7 @@ struct usb_interface { unsigned num_altsetting; /* number of alternate settings */ int minor; /* minor number this interface is bound to */ + enum usb_interface_condition condition; /* state of binding */ struct device dev; /* interface specific device info */ struct class_device *class_dev; }; @@ -341,11 +351,12 @@ extern void usb_put_dev(struct usb_device *dev); extern void usb_lock_device(struct usb_device *udev); extern int usb_trylock_device(struct usb_device *udev); +extern int usb_lock_device_for_reset(struct usb_device *udev, + struct usb_interface *iface); extern void usb_unlock_device(struct usb_device *udev); -/* mostly for devices emulating SCSI over USB */ +/* USB port reset for device reinitialization */ extern int usb_reset_device(struct usb_device *dev); -extern int __usb_reset_device(struct usb_device *dev); extern struct usb_device *usb_find_device(u16 vendor_id, u16 product_id); -- cgit v1.2.3 From 5a2a6de557b8b4c98d40697da256a6533d2565cd Mon Sep 17 00:00:00 2001 From: Luca Risolia Date: Mon, 13 Sep 2004 20:51:00 -0700 Subject: [PATCH] USB: SN9C10x driver updates SN9C10x driver updates. Changes: (+ new, - removed, * cleanup, @ bugfix, = sync with kernels) @ Create correct red,green,blue entries under /sys according to the detected bridge * Add and use defined symbols for I2C slave ids of TAS5110C1B and TAS51130D1B * Color fixes for PAS202BCB - from its maintainer - Signed-off-by: Luca Risolia Signed-off-by: Greg Kroah-Hartman --- Documentation/usb/sn9c102.txt | 11 ++++++++--- drivers/usb/media/sn9c102.h | 8 ++++---- drivers/usb/media/sn9c102_core.c | 30 +++++++++++++++++++++++------- drivers/usb/media/sn9c102_pas202bcb.c | 9 +++++---- drivers/usb/media/sn9c102_sensor.h | 3 +++ drivers/usb/media/sn9c102_tas5110c1b.c | 4 ++-- drivers/usb/media/sn9c102_tas5130d1b.c | 4 ++-- 7 files changed, 47 insertions(+), 22 deletions(-) diff --git a/Documentation/usb/sn9c102.txt b/Documentation/usb/sn9c102.txt index ea4f4274b371..40c2f73a707f 100644 --- a/Documentation/usb/sn9c102.txt +++ b/Documentation/usb/sn9c102.txt @@ -123,12 +123,12 @@ analyze kernel messages and verify that the loading process has gone well: Module parameters are listed below: ------------------------------------------------------------------------------- Name: video_nr -Type: int array (min = 0, max = 32) +Type: int array (min = 0, max = 64) Syntax: <-1|n[,...]> Description: Specify V4L2 minor mode number: -1 = use next available n = use minor number n - You can specify up to 32 cameras this way. + You can specify up to 64 cameras this way. For example: video_nr=-1,2,-1 would assign minor number 2 to the second recognized camera and use auto for the first one and for every @@ -180,7 +180,9 @@ identifier - of the camera registered as "/dev/video0": [root@localhost #] echo 1 > i2c_reg [root@localhost #] cat i2c_val -Now let's set the green gain's register of the SN9C10x chip to 2: +Note that "cat" will fail if sensor registers cannot be read. + +Now let's set the green gain's register of the SN9C101 or SN9C102 chips to 2: [root@localhost #] echo 0x11 > reg [root@localhost #] echo 2 > val @@ -250,6 +252,9 @@ PAS202BCB PixArt Imaging Inc. TAS5110C1B Taiwan Advanced Sensor Corporation TAS5130D1B Taiwan Advanced Sensor Corporation +All the available control settings of each image sensor are supported through +the V4L2 interface. + If you think your camera is based on the above hardware and is not actually listed in the above table, you may try to add the specific USB VendorID and ProductID identifiers to the sn9c102_id_table[] in the file "sn9c102_sensor.h"; diff --git a/drivers/usb/media/sn9c102.h b/drivers/usb/media/sn9c102.h index 050a25d6f6aa..a682dfc2f747 100644 --- a/drivers/usb/media/sn9c102.h +++ b/drivers/usb/media/sn9c102.h @@ -53,8 +53,8 @@ #define SN9C102_MODULE_AUTHOR "(C) 2004 Luca Risolia" #define SN9C102_AUTHOR_EMAIL "" #define SN9C102_MODULE_LICENSE "GPL" -#define SN9C102_MODULE_VERSION "1:1.10" -#define SN9C102_MODULE_VERSION_CODE KERNEL_VERSION(1, 0, 10) +#define SN9C102_MODULE_VERSION "1:1.12" +#define SN9C102_MODULE_VERSION_CODE KERNEL_VERSION(1, 0, 12) enum sn9c102_bridge { BRIDGE_SN9C101 = 0x01, @@ -62,8 +62,8 @@ enum sn9c102_bridge { BRIDGE_SN9C103 = 0x04, }; -SN9C102_ID_TABLE; -SN9C102_SENSOR_TABLE; +SN9C102_ID_TABLE +SN9C102_SENSOR_TABLE enum sn9c102_frame_state { F_UNUSED, diff --git a/drivers/usb/media/sn9c102_core.c b/drivers/usb/media/sn9c102_core.c index 3881ae5ea08f..6d428a5b8d03 100644 --- a/drivers/usb/media/sn9c102_core.c +++ b/drivers/usb/media/sn9c102_core.c @@ -400,7 +400,7 @@ sn9c102_i2c_try_raw_write(struct sn9c102_device* cam, if (err) DBG(3, "I2C write failed for %s image sensor", sensor->name) - PDBGG("I2C write: %u bytes, data0 = 0x%02X, data1 = 0x%02X, " + PDBGG("I2C raw write: %u bytes, data0 = 0x%02X, data1 = 0x%02X, " "data2 = 0x%02X, data3 = 0x%02X, data4 = 0x%02X, data5 = 0x%02X", n, data0, data1, data2, data3, data4, data5) @@ -634,7 +634,7 @@ static int sn9c102_start_transfer(struct sn9c102_device* cam) struct usb_device *udev = cam->usbdev; struct urb* urb; const unsigned int wMaxPacketSize[] = {0, 128, 256, 384, 512, - 680, 800, 900, 1023}; + 680, 800, 900, 1023}; const unsigned int psz = wMaxPacketSize[SN9C102_ALTERNATE_SETTING]; s8 i, j; int err = 0; @@ -965,6 +965,11 @@ static ssize_t sn9c102_show_i2c_val(struct class_device* cd, char* buf) return -ENODEV; } + if (cam->sensor->slave_read_id == SN9C102_I2C_SLAVEID_UNAVAILABLE) { + up(&sn9c102_sysfs_lock); + return -ENOSYS; + } + if ((val = sn9c102_i2c_read(cam, cam->sysfs.i2c_reg)) < 0) { up(&sn9c102_sysfs_lock); return -EIO; @@ -1126,7 +1131,8 @@ static void sn9c102_create_sysfs(struct sn9c102_device* cam) video_device_create_file(v4ldev, &class_device_attr_blue); video_device_create_file(v4ldev, &class_device_attr_red); } - if (cam->sensor->slave_write_id && cam->sensor->slave_read_id) { + if (cam->sensor->slave_write_id != SN9C102_I2C_SLAVEID_UNAVAILABLE || + cam->sensor->slave_read_id != SN9C102_I2C_SLAVEID_UNAVAILABLE) { video_device_create_file(v4ldev, &class_device_attr_i2c_reg); video_device_create_file(v4ldev, &class_device_attr_i2c_val); } @@ -2362,10 +2368,20 @@ sn9c102_usb_probe(struct usb_interface* intf, const struct usb_device_id* id) } cam->bridge = (sn9c102_id_table[i].idProduct & 0xffc0) == 0x6080 ? - BRIDGE_SN9C102 : BRIDGE_SN9C103; - - DBG(2, "SN9C10x PC Camera Controller detected (vid/pid 0x%04X/0x%04X)", - sn9c102_id_table[i].idVendor, sn9c102_id_table[i].idProduct) + BRIDGE_SN9C103 : BRIDGE_SN9C102; + switch (cam->bridge) { + case BRIDGE_SN9C101: + case BRIDGE_SN9C102: + DBG(2, "SN9C10[12] PC Camera Controller detected " + "(vid/pid 0x%04X/0x%04X)", sn9c102_id_table[i].idVendor, + sn9c102_id_table[i].idProduct) + break; + case BRIDGE_SN9C103: + DBG(2, "SN9C103 PC Camera Controller detected " + "(vid/pid 0x%04X/0x%04X)", sn9c102_id_table[i].idVendor, + sn9c102_id_table[i].idProduct) + break; + } for (i = 0; sn9c102_sensor_table[i]; i++) { err = sn9c102_sensor_table[i](cam); diff --git a/drivers/usb/media/sn9c102_pas202bcb.c b/drivers/usb/media/sn9c102_pas202bcb.c index ffd12ed14e34..72063e885871 100644 --- a/drivers/usb/media/sn9c102_pas202bcb.c +++ b/drivers/usb/media/sn9c102_pas202bcb.c @@ -36,18 +36,19 @@ static int pas202bcb_init(struct sn9c102_device* cam) err += sn9c102_write_reg(cam, 0x00, 0x11); err += sn9c102_write_reg(cam, 0x00, 0x14); err += sn9c102_write_reg(cam, 0x20, 0x17); - err += sn9c102_write_reg(cam, 0x20, 0x19); + err += sn9c102_write_reg(cam, 0x30, 0x19); err += sn9c102_write_reg(cam, 0x09, 0x18); - err += sn9c102_i2c_write(cam, 0x02, 0x0c); + err += sn9c102_i2c_write(cam, 0x02, 0x14); err += sn9c102_i2c_write(cam, 0x03, 0x40); err += sn9c102_i2c_write(cam, 0x04, 0x07); err += sn9c102_i2c_write(cam, 0x05, 0x25); err += sn9c102_i2c_write(cam, 0x0d, 0x2c); err += sn9c102_i2c_write(cam, 0x0e, 0x01); err += sn9c102_i2c_write(cam, 0x0f, 0xa9); - err += sn9c102_i2c_write(cam, 0x08, 0x01); + err += sn9c102_i2c_write(cam, 0x10, 0x08); err += sn9c102_i2c_write(cam, 0x0b, 0x01); + err += sn9c102_i2c_write(cam, 0x0c, 0x04); err += sn9c102_i2c_write(cam, 0x13, 0x63); err += sn9c102_i2c_write(cam, 0x15, 0x70); err += sn9c102_i2c_write(cam, 0x11, 0x01); @@ -217,7 +218,7 @@ int sn9c102_probe_pas202bcb(struct sn9c102_device* cam) * NOTE: do NOT change the values! */ err += sn9c102_write_reg(cam, 0x01, 0x01); /* sensor power down */ - err += sn9c102_write_reg(cam, 0x00, 0x01); /* sensor power on */ + err += sn9c102_write_reg(cam, 0x40, 0x01); /* sensor power on */ err += sn9c102_write_reg(cam, 0x28, 0x17); /* sensor clock at 24 MHz */ if (err) return -EIO; diff --git a/drivers/usb/media/sn9c102_sensor.h b/drivers/usb/media/sn9c102_sensor.h index 9632a5a63ccc..01bcad14ca6d 100644 --- a/drivers/usb/media/sn9c102_sensor.h +++ b/drivers/usb/media/sn9c102_sensor.h @@ -186,6 +186,9 @@ enum sn9c102_i2c_interface { SN9C102_I2C_3WIRES, }; +#define SN9C102_I2C_SLAVEID_FICTITIOUS 0xff +#define SN9C102_I2C_SLAVEID_UNAVAILABLE 0x00 + struct sn9c102_sensor { char name[32], /* sensor name */ maintainer[64]; /* name of the mantainer */ diff --git a/drivers/usb/media/sn9c102_tas5110c1b.c b/drivers/usb/media/sn9c102_tas5110c1b.c index 006a6b569f4a..ce8b47b59a75 100644 --- a/drivers/usb/media/sn9c102_tas5110c1b.c +++ b/drivers/usb/media/sn9c102_tas5110c1b.c @@ -104,8 +104,8 @@ static struct sn9c102_sensor tas5110c1b = { .maintainer = "Luca Risolia ", .frequency = SN9C102_I2C_100KHZ, .interface = SN9C102_I2C_3WIRES, - .slave_read_id = 0xff, /* fictitious */ - .slave_write_id = 0xff, /* fictitious */ + .slave_read_id = SN9C102_I2C_SLAVEID_UNAVAILABLE, + .slave_write_id = SN9C102_I2C_SLAVEID_FICTITIOUS, .init = &tas5110c1b_init, .qctrl = { { diff --git a/drivers/usb/media/sn9c102_tas5130d1b.c b/drivers/usb/media/sn9c102_tas5130d1b.c index 7af73acd2c37..0048e9c20d80 100644 --- a/drivers/usb/media/sn9c102_tas5130d1b.c +++ b/drivers/usb/media/sn9c102_tas5130d1b.c @@ -109,8 +109,8 @@ static struct sn9c102_sensor tas5130d1b = { .maintainer = "Luca Risolia ", .frequency = SN9C102_I2C_100KHZ, .interface = SN9C102_I2C_3WIRES, - .slave_read_id = 0xff, /* fictitious */ - .slave_write_id = 0xff, /* fictitious */ + .slave_read_id = SN9C102_I2C_SLAVEID_UNAVAILABLE, + .slave_write_id = SN9C102_I2C_SLAVEID_FICTITIOUS, .init = &tas5130d1b_init, .qctrl = { { -- cgit v1.2.3 From 1db17dc9cc37941ffd1fff8f63db004eff182c94 Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Mon, 13 Sep 2004 21:23:52 -0700 Subject: [PATCH] USB: add missing pci_disable_device for PCI-based USB HCD From: Kenji Kaneshige This patch adds pci_disable_device() into usb_hcd_pci_remove(). If the driver decides to stop using the device, it should call pci_disable_device() to deallocate any IRQ resources, disable PCI bus-mastering, etc. Signed-off-by: Kenji Kaneshige Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hcd-pci.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index f6f9c3b2325c..163345016aa2 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c @@ -260,6 +260,8 @@ void usb_hcd_pci_remove (struct pci_dev *dev) } usb_deregister_bus (&hcd->self); + + pci_disable_device(dev); } EXPORT_SYMBOL (usb_hcd_pci_remove); -- cgit v1.2.3 From 36b0e6a79d56563683a81b24c493b84937056ca1 Mon Sep 17 00:00:00 2001 From: Eric Valette Date: Mon, 13 Sep 2004 21:54:59 -0700 Subject: [PATCH] USB: rtl8150.c ethernet driver : usb_unlink_urb ->usb_kill_urb While we are looking at this driver, here is a way to avoid one full page of annoying messages at shutdown/module unload. Signed-off-by: Eric Valette Signed-off-by: Greg Kroah-Hartman --- drivers/usb/net/rtl8150.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/usb/net/rtl8150.c b/drivers/usb/net/rtl8150.c index c7daf82e3a34..4caad655aeb0 100644 --- a/drivers/usb/net/rtl8150.c +++ b/drivers/usb/net/rtl8150.c @@ -344,7 +344,7 @@ static int rtl8150_set_mac_address(struct net_device *netdev, void *p) static int rtl8150_reset(rtl8150_t * dev) { - u8 data = 0x11; + u8 data = 0x10; int i = HZ; set_registers(dev, CR, 1, &data); @@ -392,10 +392,10 @@ static void free_all_urbs(rtl8150_t * dev) static void unlink_all_urbs(rtl8150_t * dev) { - usb_unlink_urb(dev->rx_urb); - usb_unlink_urb(dev->tx_urb); - usb_unlink_urb(dev->intr_urb); - usb_unlink_urb(dev->ctrl_urb); + usb_kill_urb(dev->rx_urb); + usb_kill_urb(dev->tx_urb); + usb_kill_urb(dev->intr_urb); + usb_kill_urb(dev->ctrl_urb); } static inline struct sk_buff *pull_skb(rtl8150_t *dev) @@ -663,7 +663,7 @@ static void rtl8150_tx_timeout(struct net_device *netdev) return; warn("%s: Tx timeout.", netdev->name); dev->tx_urb->transfer_flags |= URB_ASYNC_UNLINK; - usb_unlink_urb(dev->tx_urb); + usb_kill_urb(dev->tx_urb); dev->stats.tx_errors++; } -- cgit v1.2.3 From b51f163dd33b10311d61f2e2b55107d0a316cac5 Mon Sep 17 00:00:00 2001 From: Pete Zaitcev Date: Mon, 13 Sep 2004 21:56:06 -0700 Subject: [PATCH] USB: Patch for 3 ub bugs in 2.6.9-rc1-mm4 Actual users of ub quickly found problems, so here's a patch to address some of them. #1: An attempt to mount a CF card, pull the plug, then unmount causes a message "getblk: bad sector size 512" and an oops. This is caused by trying to do put_disk from disconnect instead of using a reference count. The sd.c does it this way (it uses kref). #2: The hald fills /var/log/messages with block device errors. It seems that it happens because ub allowed opens of known offline devices, and then partition checking produced those errors. I hope taking code from sd.c should fix it. Also I replaced usb_unlink_urb with usb_kill_urb. Signed-off-by: Greg Kroah-Hartman --- drivers/block/ub.c | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/drivers/block/ub.c b/drivers/block/ub.c index 6379b9043631..184b8a22e86f 100644 --- a/drivers/block/ub.c +++ b/drivers/block/ub.c @@ -490,6 +490,18 @@ static void ub_id_put(int id) */ static void ub_cleanup(struct ub_dev *sc) { + + /* + * If we zero disk->private_data BEFORE put_disk, we have to check + * for NULL all over the place in open, release, check_media and + * revalidate, because the block level semaphore is well inside the + * put_disk. But we cannot zero after the call, because *disk is gone. + * The sd.c is blatantly racy in this area. + */ + /* disk->private_data = NULL; */ + put_disk(sc->disk); + sc->disk = NULL; + ub_id_put(sc->id); kfree(sc); } @@ -1413,7 +1425,15 @@ static int ub_bd_open(struct inode *inode, struct file *filp) if (sc->removable || sc->readonly) check_disk_change(inode->i_bdev); - /* XXX sd.c and floppy.c bail on open if media is not present. */ + /* + * The sd.c considers ->media_present and ->changed not equivalent, + * under some pretty murky conditions (a failure of READ CAPACITY). + * We may need it one day. + */ + if (sc->removable && sc->changed && !(filp->f_flags & O_NDELAY)) { + rc = -ENOMEDIUM; + goto err_open; + } if (sc->readonly && (filp->f_mode & FMODE_WRITE)) { rc = -EROFS; @@ -1498,8 +1518,11 @@ static int ub_bd_revalidate(struct gendisk *disk) printk(KERN_INFO "%s: device %u capacity nsec %ld bsize %u\n", sc->name, sc->dev->devnum, sc->capacity.nsec, sc->capacity.bsize); + /* XXX Support sector size switching like in sr.c */ + // blk_queue_hardsect_size(q, sc->capacity.bsize); set_capacity(disk, sc->capacity.nsec); // set_disk_ro(sdkp->disk, sc->readonly); + return 0; } @@ -1746,12 +1769,7 @@ static int ub_probe_clear_stall(struct ub_dev *sc, int stalled_pipe) wait_for_completion(&compl); del_timer_sync(&timer); - /* - * Most of the time, URB was done and dev set to NULL, and so - * the unlink bounces out with ENODEV. We do not call usb_kill_urb - * because we still think about a backport to 2.4. - */ - usb_unlink_urb(&sc->work_urb); + usb_kill_urb(&sc->work_urb); /* reset the endpoint toggle */ usb_settoggle(sc->dev, endp, usb_pipeout(sc->last_pipe), 0); @@ -2010,17 +2028,6 @@ static void ub_disconnect(struct usb_interface *intf) if (q) blk_cleanup_queue(q); - /* - * If we zero disk->private_data BEFORE put_disk, we have to check - * for NULL all over the place in open, release, check_media and - * revalidate, because the block level semaphore is well inside the - * put_disk. But we cannot zero after the call, because *disk is gone. - * The sd.c is blatantly racy in this area. - */ - /* disk->private_data = NULL; */ - put_disk(disk); - sc->disk = NULL; - /* * We really expect blk_cleanup_queue() to wait, so no amount * of paranoya is too much. -- cgit v1.2.3 From 73c858138787db9287673e2daa366ba53ab49b32 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Mon, 13 Sep 2004 21:58:23 -0700 Subject: [PATCH] USB: acm work around for misplaced this implements a work around for some devices which have correct extra descriptors, but misplace them. Signed-Off-By: Oliver Neukum Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-acm.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 37af64263c1b..b174d35b8d88 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -547,6 +547,17 @@ static int acm_probe (struct usb_interface *intf, return -EINVAL; } + if (!buflen) { + if (intf->cur_altsetting->endpoint->extralen && intf->cur_altsetting->endpoint->extra) { + dev_dbg(&intf->dev,"Seeking extra descriptors on endpoint"); + buflen = intf->cur_altsetting->endpoint->extralen; + buffer = intf->cur_altsetting->endpoint->extra; + } else { + err("Zero length descriptor references"); + return -EINVAL; + } + } + while (buflen > 0) { if (buffer [1] != USB_DT_CS_INTERFACE) { err("skipping garbage"); -- cgit v1.2.3 From 190b57996ef1d8d0520af964f0bbd92976aa341a Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Mon, 13 Sep 2004 22:00:01 -0700 Subject: [PATCH] USB: Descriptor listing bugfix for g_file_storage This patch repairs a mistake I made when adding OTG support to the file-storage gadget. All the descriptor entries were bumped up by one, which caused a problem during initialization. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/file_storage.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/file_storage.c b/drivers/usb/gadget/file_storage.c index 2d2b2c032f19..6987b7c3e7c3 100644 --- a/drivers/usb/gadget/file_storage.c +++ b/drivers/usb/gadget/file_storage.c @@ -929,6 +929,7 @@ static const struct usb_descriptor_header *fs_function[] = { (struct usb_descriptor_header *) &fs_intr_in_desc, NULL, }; +#define FS_FUNCTION_PRE_EP_ENTRIES 2 #ifdef CONFIG_USB_GADGET_DUALSPEED @@ -992,6 +993,7 @@ static const struct usb_descriptor_header *hs_function[] = { (struct usb_descriptor_header *) &hs_intr_in_desc, NULL, }; +#define HS_FUNCTION_PRE_EP_ENTRIES 2 /* Maxpacket and other transfer characteristics vary by speed. */ #define ep_desc(g,fs,hs) (((g)->speed==USB_SPEED_HIGH) ? (hs) : (fs)) @@ -3899,10 +3901,10 @@ static int __init fsg_bind(struct usb_gadget *gadget) intf_desc.bNumEndpoints = i; intf_desc.bInterfaceSubClass = mod_data.protocol_type; intf_desc.bInterfaceProtocol = mod_data.transport_type; - fs_function[i+1] = NULL; + fs_function[i + FS_FUNCTION_PRE_EP_ENTRIES] = NULL; #ifdef CONFIG_USB_GADGET_DUALSPEED - hs_function[i+1] = NULL; + hs_function[i + HS_FUNCTION_PRE_EP_ENTRIES] = NULL; /* Assume ep0 uses the same maxpacket value for both speeds */ dev_qualifier.bMaxPacketSize0 = fsg->ep0->maxpacket; -- cgit v1.2.3 From 3bac8d2b031552e0cf00985590cd7714b8b290b1 Mon Sep 17 00:00:00 2001 From: Luiz Capitulino Date: Mon, 13 Sep 2004 22:00:27 -0700 Subject: [PATCH] USB: remove ugly code from usb/serial/usb-serial.c. This patch removes ugly code from some function in usb/serial/usb-serial.c which is using a goto statement intead of a simple `return'. To be true, I'm not certain if there is a special reason to do that, if so ignore me. ;) Signed-off-by: Luiz Capitulino Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/usb-serial.c | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index 6b2aa1f6aa86..aa5c226197f7 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -621,15 +621,12 @@ static void serial_throttle (struct tty_struct * tty) if (!port->open_count) { dbg ("%s - port not open", __FUNCTION__); - goto exit; + return; } /* pass on to the driver specific version of this function */ if (port->serial->type->throttle) port->serial->type->throttle(port); - -exit: - ; } static void serial_unthrottle (struct tty_struct * tty) @@ -640,15 +637,12 @@ static void serial_unthrottle (struct tty_struct * tty) if (!port->open_count) { dbg("%s - port not open", __FUNCTION__); - goto exit; + return; } /* pass on to the driver specific version of this function */ if (port->serial->type->unthrottle) port->serial->type->unthrottle(port); - -exit: - ; } static int serial_ioctl (struct tty_struct *tty, struct file * file, unsigned int cmd, unsigned long arg) @@ -681,15 +675,12 @@ static void serial_set_termios (struct tty_struct *tty, struct termios * old) if (!port->open_count) { dbg("%s - port not open", __FUNCTION__); - goto exit; + return; } /* pass on to the driver specific version of this function if it is available */ if (port->serial->type->set_termios) port->serial->type->set_termios(port, old); - -exit: - ; } static void serial_break (struct tty_struct *tty, int break_state) @@ -700,15 +691,12 @@ static void serial_break (struct tty_struct *tty, int break_state) if (!port->open_count) { dbg("%s - port not open", __FUNCTION__); - goto exit; + return; } /* pass on to the driver specific version of this function if it is available */ if (port->serial->type->break_ctl) port->serial->type->break_ctl(port, break_state); - -exit: - ; } static int serial_read_proc (char *page, char **start, off_t off, int count, int *eof, void *data) -- cgit v1.2.3 From 2290c3778a6a3cf84f73580fd8e4cafcf63fa21d Mon Sep 17 00:00:00 2001 From: Luiz Capitulino Date: Mon, 13 Sep 2004 22:00:53 -0700 Subject: [PATCH] USB: missing check in usb/serial/usb-serial.c. This patch add a missing check in the call to bus_register() and not initialise `result' (which is not necessary). Signed-off-by: Luiz Capitulino Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/usb-serial.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index aa5c226197f7..73efa4270b79 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -1217,7 +1217,7 @@ struct tty_driver *usb_serial_tty_driver; static int __init usb_serial_init(void) { int i; - int result = 0; + int result; usb_serial_tty_driver = alloc_tty_driver(SERIAL_TTY_MINORS); if (!usb_serial_tty_driver) @@ -1228,13 +1228,17 @@ static int __init usb_serial_init(void) serial_table[i] = NULL; } - bus_register(&usb_serial_bus_type); + result = bus_register(&usb_serial_bus_type); + if (result) { + err("%s - registering bus driver failed", __FUNCTION__); + goto exit_bus; + } /* register the generic driver, if we should */ result = usb_serial_generic_register(debug); if (result < 0) { err("%s - registering generic driver failed", __FUNCTION__); - goto exit; + goto exit_generic; } usb_serial_tty_driver->owner = THIS_MODULE; @@ -1252,7 +1256,7 @@ static int __init usb_serial_init(void) result = tty_register_driver(usb_serial_tty_driver); if (result) { err("%s - tty_register_driver failed", __FUNCTION__); - goto exit_generic; + goto exit_reg_driver; } /* register the USB driver */ @@ -1269,10 +1273,13 @@ static int __init usb_serial_init(void) exit_tty: tty_unregister_driver(usb_serial_tty_driver); -exit_generic: +exit_reg_driver: usb_serial_generic_deregister(); -exit: +exit_generic: + bus_unregister(&usb_serial_bus_type); + +exit_bus: err ("%s - returning with error %d", __FUNCTION__, result); put_tty_driver(usb_serial_tty_driver); return result; -- cgit v1.2.3 From 2fd4d42b1a104a15beb9c8434f256a0a8ab83f38 Mon Sep 17 00:00:00 2001 From: Catalin Boie Date: Mon, 13 Sep 2004 22:06:27 -0700 Subject: [PATCH] USB: cdc-acm-usb-use-uninit-mem-bug.patch Attached is a patch for cdc-acm out of bounds access. Signed-off-by: Catalin(ux aka Dino) BOIE Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-acm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index b174d35b8d88..5614dde2c751 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -653,7 +653,7 @@ next_desc: dbg("interfaces are valid"); for (minor = 0; minor < ACM_TTY_MINORS && acm_table[minor]; minor++); - if (acm_table[minor]) { + if (minor == ACM_TTY_MINORS) { err("no more free acm devices"); return -ENODEV; } -- cgit v1.2.3 From 808f57d45fafe80fd64d1065a24b354f4aa452b0 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Mon, 13 Sep 2004 22:16:46 -0700 Subject: [PATCH] USB: Allow device resets for hubs This patch adds support for calling usb_reset_device() on hubs (other than root hubs). It uses some of the new routines added recently by David Brownell to shorten and simplify the code. The only place where this is called is if khubd encounters a serious error with a hub, so I don't expect it to turn up very often in ordinary use. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 82 ++++++++++++++++++-------------------------------- 1 file changed, 29 insertions(+), 53 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 848ab62e71a6..c1ba248b6aac 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -651,8 +651,6 @@ static void hub_quiesce(struct usb_hub *hub) flush_scheduled_work(); } -#ifdef CONFIG_USB_SUSPEND - static void hub_reactivate(struct usb_hub *hub) { int status; @@ -665,8 +663,6 @@ static void hub_reactivate(struct usb_hub *hub) schedule_delayed_work(&hub->leds, LED_CYCLE_PERIOD); } -#endif - static void hub_disconnect(struct usb_interface *intf) { struct usb_hub *hub = usb_get_intfdata (intf); @@ -808,54 +804,26 @@ hub_ioctl(struct usb_interface *intf, unsigned int code, void *user_data) } } -/* caller has locked the hub and must own the device lock */ -static int hub_reset(struct usb_hub *hub) +/* caller has locked the hub device */ +static void hub_pre_reset(struct usb_device *hdev) { - struct usb_device *hdev = hub->hdev; + struct usb_hub *hub = usb_get_intfdata(hdev->actconfig->interface[0]); int i; - /* Disconnect any attached devices */ - for (i = 0; i < hub->descriptor->bNbrPorts; i++) { + for (i = 0; i < hdev->maxchild; ++i) { if (hdev->children[i]) usb_disconnect(&hdev->children[i]); } - - /* Attempt to reset the hub */ - if (hub->urb) - usb_kill_urb(hub->urb); - else - return -1; - - if (usb_reset_device(hdev)) - return -1; - - hub->urb->dev = hdev; - if (usb_submit_urb(hub->urb, GFP_KERNEL)) - return -1; - - hub_power_on(hub); - - return 0; + hub_quiesce(hub); } -/* caller has locked the hub */ -/* FIXME! This routine should be subsumed into hub_reset */ -static void hub_start_disconnect(struct usb_device *hdev) +/* caller has locked the hub device */ +static void hub_post_reset(struct usb_device *hdev) { - struct usb_device *parent = hdev->parent; - int i; - - /* Find the device pointer to disconnect */ - if (parent) { - for (i = 0; i < parent->maxchild; i++) { - if (parent->children[i] == hdev) { - usb_disconnect(&parent->children[i]); - return; - } - } - } + struct usb_hub *hub = usb_get_intfdata(hdev->actconfig->interface[0]); - dev_err(&hdev->dev, "cannot disconnect hub!\n"); + hub_reactivate(hub); + hub_power_on(hub); } @@ -2493,10 +2461,10 @@ static void hub_events(void) dev_dbg (hub_dev, "resetting for error %d\n", hub->error); - if (hub_reset(hub)) { + ret = usb_reset_device(hdev); + if (ret) { dev_dbg (hub_dev, - "can't reset; disconnecting\n"); - hub_start_disconnect(hdev); + "error resetting hub: %d\n", ret); goto loop; } @@ -2767,6 +2735,7 @@ int usb_reset_device(struct usb_device *udev) struct usb_device *parent = udev->parent; struct usb_device_descriptor descriptor = udev->descriptor; int i, ret, port = -1; + int udev_is_a_hub = 0; if (udev->state == USB_STATE_NOTATTACHED || udev->state == USB_STATE_SUSPENDED) { @@ -2775,13 +2744,9 @@ int usb_reset_device(struct usb_device *udev) return -EINVAL; } - /* FIXME: This should be legal for regular hubs. Root hubs may - * have special requirements. */ - if (udev->maxchild) { - /* this requires hub- or hcd-specific logic; - * see hub_reset() and OHCI hc_restart() - */ - dev_dbg(&udev->dev, "%s for hub!\n", __FUNCTION__); + if (!parent) { + /* this requires hcd-specific logic; see OHCI hc_restart() */ + dev_dbg(&udev->dev, "%s for root hub!\n", __FUNCTION__); return -EISDIR; } @@ -2797,6 +2762,14 @@ int usb_reset_device(struct usb_device *udev) return -ENOENT; } + /* If we're resetting an active hub, take some special actions */ + if (udev->actconfig && + udev->actconfig->interface[0]->dev.driver == + &hub_driver.driver) { + udev_is_a_hub = 1; + hub_pre_reset(udev); + } + /* ep0 maxpacket size may change; let the HCD know about it. * Other endpoints will be handled by re-enumeration. */ usb_disable_endpoint(udev, 0); @@ -2815,7 +2788,7 @@ int usb_reset_device(struct usb_device *udev) } if (!udev->actconfig) - return 0; + goto done; ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), USB_REQ_SET_CONFIGURATION, 0, @@ -2849,6 +2822,9 @@ int usb_reset_device(struct usb_device *udev) } } +done: + if (udev_is_a_hub) + hub_post_reset(udev); return 0; re_enumerate: -- cgit v1.2.3 From cd1f76678b398c0e84815177902414a2a2122729 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 14 Sep 2004 00:49:59 -0700 Subject: PCI: fix __iomem warnings in quirk code Signed-off-by: Greg Kroah-Hartman --- drivers/pci/quirks.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 34f072c9823e..8ded4fe07f51 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -921,7 +921,7 @@ static void __init quirk_usb_handoff_uhci(struct pci_dev *pdev) static void __init quirk_usb_handoff_ohci(struct pci_dev *pdev) { - char *base; + void __iomem *base; int wait_time; base = ioremap_nocache(pci_resource_start(pdev, 0), @@ -952,7 +952,7 @@ static void __init quirk_usb_handoff_ohci(struct pci_dev *pdev) static void __init quirk_usb_disable_ehci(struct pci_dev *pdev) { int wait_time, delta; - char *base, *op_reg_base; + void __iomem *base, *op_reg_base; u32 hcc_params, val, temp; u8 cap_length; -- cgit v1.2.3 From f6ab701ab0b8e4b1acf4bcdae64592d1598755dd Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 14 Sep 2004 21:52:18 -0700 Subject: USB: fix hcd-pci's __iomem warnings Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hcd-pci.c | 4 ++-- drivers/usb/core/hcd.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index 163345016aa2..837bb267f194 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c @@ -65,7 +65,7 @@ int usb_hcd_pci_probe (struct pci_dev *dev, const struct pci_device_id *id) { struct hc_driver *driver; unsigned long resource, len; - void *base; + void __iomem *base; struct usb_hcd *hcd; int retval, region; char buf [8], *bufp = buf; @@ -121,7 +121,7 @@ clean_1: dev_dbg (&dev->dev, "no i/o regions available\n"); return -EBUSY; } - base = (void *) resource; + base = (void __iomem *) resource; } // driver->reset(), later on, will transfer device from diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h index 5e0337dfbcc9..eb8ae109096e 100644 --- a/drivers/usb/core/hcd.h +++ b/drivers/usb/core/hcd.h @@ -76,7 +76,7 @@ struct usb_hcd { /* usb_bus.hcpriv points to this */ unsigned can_wakeup:1; /* hw supports wakeup? */ unsigned remote_wakeup:1;/* sw should use wakeup? */ int irq; /* irq allocated */ - void *regs; /* device memory/io */ + void __iomem *regs; /* device memory/io */ #ifdef CONFIG_PCI int region; /* pci region for regs */ -- cgit v1.2.3 From 3e4e0e447be544b3bab52524f95bbfa2d4c11749 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 14 Sep 2004 23:07:28 -0700 Subject: USB: fix up __iomem warnings in the ehci driver. Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-hcd.c | 7 +++---- drivers/usb/host/ehci.h | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 2f204b864a90..03d427a672a4 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -155,7 +155,7 @@ MODULE_PARM_DESC (log2_irq_thresh, "log2 IRQ latency, 1-64 microframes"); * before driver shutdown. But it also seems to be caused by bugs in cardbus * bridge shutdown: shutting down the bridge before the devices using it. */ -static int handshake (u32 *ptr, u32 mask, u32 done, int usec) +static int handshake (void __iomem *ptr, u32 mask, u32 done, int usec) { u32 result; @@ -340,9 +340,8 @@ static int ehci_hc_reset (struct usb_hcd *hcd) spin_lock_init (&ehci->lock); - ehci->caps = (struct ehci_caps *) hcd->regs; - ehci->regs = (struct ehci_regs *) (hcd->regs + - HC_LENGTH (readl (&ehci->caps->hc_capbase))); + ehci->caps = hcd->regs; + ehci->regs = hcd->regs + HC_LENGTH (readl (&ehci->caps->hc_capbase)); dbg_hcs_params (ehci, "reset"); dbg_hcc_params (ehci, "reset"); diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index de666f6b3314..c66b8d132d66 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -71,8 +71,8 @@ struct ehci_hcd { /* one per controller */ /* glue to PCI and HCD framework */ struct usb_hcd hcd; - struct ehci_caps *caps; - struct ehci_regs *regs; + struct ehci_caps __iomem *caps; + struct ehci_regs __iomem *regs; u32 hcs_params; /* cached register copy */ /* per-HC memory pools (could be per-bus, but ...) */ -- cgit v1.2.3 From 2d31f96408789bdbe7bd030308a3cd9dca45fe2e Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 14 Sep 2004 23:17:50 -0700 Subject: USB: fix up __iomem warnings in the ohci driver. Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-dbg.c | 4 ++-- drivers/usb/host/ohci-hcd.c | 2 +- drivers/usb/host/ohci-hub.c | 2 +- drivers/usb/host/ohci.h | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/usb/host/ohci-dbg.c b/drivers/usb/host/ohci-dbg.c index 532164079cfa..271908e82f0e 100644 --- a/drivers/usb/host/ohci-dbg.c +++ b/drivers/usb/host/ohci-dbg.c @@ -131,7 +131,7 @@ static char *hcfs2string (int state) static void ohci_dump_status (struct ohci_hcd *controller, char **next, unsigned *size) { - struct ohci_regs *regs = controller->regs; + struct ohci_regs __iomem *regs = controller->regs; u32 temp; temp = ohci_readl (®s->revision) & 0xff; @@ -599,7 +599,7 @@ show_registers (struct class_device *class_dev, char *buf) struct usb_bus *bus; struct usb_hcd *hcd; struct ohci_hcd *ohci; - struct ohci_regs *regs; + struct ohci_regs __iomem *regs; unsigned long flags; unsigned temp, size; char *next; diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index c5b806265504..84867d1d0bb7 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -679,7 +679,7 @@ static int ohci_run (struct ohci_hcd *ohci) static irqreturn_t ohci_irq (struct usb_hcd *hcd, struct pt_regs *ptregs) { struct ohci_hcd *ohci = hcd_to_ohci (hcd); - struct ohci_regs *regs = ohci->regs; + struct ohci_regs __iomem *regs = ohci->regs; int ints; /* we can eliminate a (slow) ohci_readl() diff --git a/drivers/usb/host/ohci-hub.c b/drivers/usb/host/ohci-hub.c index d7a28d8eefb9..cf03c1fc0a2a 100644 --- a/drivers/usb/host/ohci-hub.c +++ b/drivers/usb/host/ohci-hub.c @@ -468,7 +468,7 @@ static void start_hnp(struct ohci_hcd *ohci); /* called from some task, normally khubd */ static inline void root_port_reset (struct ohci_hcd *ohci, unsigned port) { - u32 *portstat = &ohci->regs->roothub.portstatus [port]; + u32 __iomem *portstat = &ohci->regs->roothub.portstatus [port]; u32 temp; u16 now = readl(&ohci->regs->fmnumber); u16 reset_done = now + PORT_RESET_MSEC; diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h index 6a3fa725ef4a..5b4e0e0d753b 100644 --- a/drivers/usb/host/ohci.h +++ b/drivers/usb/host/ohci.h @@ -342,7 +342,7 @@ struct ohci_hcd { /* * I/O memory used to communicate with the HC (dma-consistent) */ - struct ohci_regs *regs; + struct ohci_regs __iomem *regs; /* * main memory used to communicate with the HC (dma-consistent). @@ -452,7 +452,7 @@ static inline unsigned int ohci_readl (void* regs) #else /* Standard version of ohci_readl uses standard, platform * specific implementation. */ -static inline unsigned int ohci_readl (void* regs) +static inline unsigned int ohci_readl (void __iomem * regs) { return readl (regs); } -- cgit v1.2.3 From d4ab1ca0f02e6c2c4d82135c1d2ca8e043435443 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 14 Sep 2004 23:25:04 -0700 Subject: USB: fix up some minor sparse warnings in the uhci driver. Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/uhci-hcd.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/usb/host/uhci-hcd.h b/drivers/usb/host/uhci-hcd.h index 3a6735740364..77efaf96a016 100644 --- a/drivers/usb/host/uhci-hcd.h +++ b/drivers/usb/host/uhci-hcd.h @@ -391,12 +391,12 @@ struct urb_priv { struct uhci_qh *qh; /* QH for this URB */ struct list_head td_list; /* P: urb->lock */ - int fsbr : 1; /* URB turned on FSBR */ - int fsbr_timeout : 1; /* URB timed out on FSBR */ - int queued : 1; /* QH was queued (not linked in) */ - int short_control_packet : 1; /* If we get a short packet during */ - /* a control transfer, retrigger */ - /* the status phase */ + unsigned fsbr : 1; /* URB turned on FSBR */ + unsigned fsbr_timeout : 1; /* URB timed out on FSBR */ + unsigned queued : 1; /* QH was queued (not linked in) */ + unsigned short_control_packet : 1; /* If we get a short packet during */ + /* a control transfer, retrigger */ + /* the status phase */ unsigned long inserttime; /* In jiffies */ unsigned long fsbrtime; /* In jiffies */ -- cgit v1.2.3 From c5cefba03a1392703e59df3f63b27245e7a7265b Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Wed, 15 Sep 2004 01:36:20 -0700 Subject: [PATCH] USB: remove calls to usb_unlink_urb in class/audio.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/audio.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/usb/class/audio.c b/drivers/usb/class/audio.c index 36b3f3663e62..88628d85dff2 100644 --- a/drivers/usb/class/audio.c +++ b/drivers/usb/class/audio.c @@ -635,13 +635,13 @@ static void usbin_stop(struct usb_audiodev *as) spin_unlock_irqrestore(&as->lock, flags); if (notkilled && signal_pending(current)) { if (i & FLG_URB0RUNNING) - usb_unlink_urb(u->durb[0].urb); + usb_kill_urb(u->durb[0].urb); if (i & FLG_URB1RUNNING) - usb_unlink_urb(u->durb[1].urb); + usb_kill_urb(u->durb[1].urb); if (i & FLG_SYNC0RUNNING) - usb_unlink_urb(u->surb[0].urb); + usb_kill_urb(u->surb[0].urb); if (i & FLG_SYNC1RUNNING) - usb_unlink_urb(u->surb[1].urb); + usb_kill_urb(u->surb[1].urb); notkilled = 0; } } @@ -1114,13 +1114,13 @@ static void usbout_stop(struct usb_audiodev *as) spin_unlock_irqrestore(&as->lock, flags); if (notkilled && signal_pending(current)) { if (i & FLG_URB0RUNNING) - usb_unlink_urb(u->durb[0].urb); + usb_kill_urb(u->durb[0].urb); if (i & FLG_URB1RUNNING) - usb_unlink_urb(u->durb[1].urb); + usb_kill_urb(u->durb[1].urb); if (i & FLG_SYNC0RUNNING) - usb_unlink_urb(u->surb[0].urb); + usb_kill_urb(u->surb[0].urb); if (i & FLG_SYNC1RUNNING) - usb_unlink_urb(u->surb[1].urb); + usb_kill_urb(u->surb[1].urb); notkilled = 0; } } -- cgit v1.2.3 From cf371fa220183d54c4486c5b53978990b2acab31 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Wed, 15 Sep 2004 01:36:51 -0700 Subject: [PATCH] USB: remove calls to usb_unlink_urb in class/bluetty.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/bluetty.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/usb/class/bluetty.c b/drivers/usb/class/bluetty.c index 2e8f5200ca40..82cf2907b79a 100644 --- a/drivers/usb/class/bluetty.c +++ b/drivers/usb/class/bluetty.c @@ -426,8 +426,8 @@ static void bluetooth_close (struct tty_struct *tty, struct file * filp) bluetooth->open_count = 0; /* shutdown any in-flight urbs that we know about */ - usb_unlink_urb (bluetooth->read_urb); - usb_unlink_urb (bluetooth->interrupt_in_urb); + usb_kill_urb (bluetooth->read_urb); + usb_kill_urb (bluetooth->interrupt_in_urb); } up(&bluetooth->lock); } @@ -705,7 +705,7 @@ void btusb_disable_bulk_read(struct tty_struct *tty){ } if ((bluetooth->read_urb) && (bluetooth->read_urb->actual_length)) - usb_unlink_urb(bluetooth->read_urb); + usb_kill_urb(bluetooth->read_urb); } #endif @@ -1187,14 +1187,14 @@ static void usb_bluetooth_disconnect(struct usb_interface *intf) bluetooth->open_count = 0; if (bluetooth->read_urb) { - usb_unlink_urb (bluetooth->read_urb); + usb_kill_urb (bluetooth->read_urb); usb_free_urb (bluetooth->read_urb); } if (bluetooth->bulk_in_buffer) kfree (bluetooth->bulk_in_buffer); if (bluetooth->interrupt_in_urb) { - usb_unlink_urb (bluetooth->interrupt_in_urb); + usb_kill_urb (bluetooth->interrupt_in_urb); usb_free_urb (bluetooth->interrupt_in_urb); } if (bluetooth->interrupt_in_buffer) @@ -1204,7 +1204,7 @@ static void usb_bluetooth_disconnect(struct usb_interface *intf) for (i = 0; i < NUM_CONTROL_URBS; ++i) { if (bluetooth->control_urb_pool[i]) { - usb_unlink_urb (bluetooth->control_urb_pool[i]); + usb_kill_urb (bluetooth->control_urb_pool[i]); if (bluetooth->control_urb_pool[i]->transfer_buffer) kfree (bluetooth->control_urb_pool[i]->transfer_buffer); usb_free_urb (bluetooth->control_urb_pool[i]); -- cgit v1.2.3 From 6aa9d4c375d6860ea639fbaa3606ae1606ecaef3 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Wed, 15 Sep 2004 01:37:16 -0700 Subject: [PATCH] USB: remove calls to usb_unlink_urb in class/cdc-acm.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-acm.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 5614dde2c751..d8fcf2f5264f 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -306,9 +306,9 @@ done: return 0; full_bailout: - usb_unlink_urb(acm->readurb); + usb_kill_urb(acm->readurb); bail_out_and_unlink: - usb_unlink_urb(acm->ctrlurb); + usb_kill_urb(acm->ctrlurb); bail_out: up(&open_sem); return -EIO; @@ -325,9 +325,9 @@ static void acm_tty_close(struct tty_struct *tty, struct file *filp) if (!--acm->used) { if (acm->dev) { acm_set_control(acm, acm->ctrlout = 0); - usb_unlink_urb(acm->ctrlurb); - usb_unlink_urb(acm->writeurb); - usb_unlink_urb(acm->readurb); + usb_kill_urb(acm->ctrlurb); + usb_kill_urb(acm->writeurb); + usb_kill_urb(acm->readurb); } else { tty_unregister_device(acm_tty_driver, acm->minor); acm_table[acm->minor] = NULL; @@ -778,9 +778,9 @@ static void acm_disconnect(struct usb_interface *intf) acm->dev = NULL; usb_set_intfdata (intf, NULL); - usb_unlink_urb(acm->ctrlurb); - usb_unlink_urb(acm->readurb); - usb_unlink_urb(acm->writeurb); + usb_kill_urb(acm->ctrlurb); + usb_kill_urb(acm->readurb); + usb_kill_urb(acm->writeurb); flush_scheduled_work(); /* wait for acm_softint */ -- cgit v1.2.3 From bf4d34766d8b9a3ef48699808ce933e7a2b963ee Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Wed, 15 Sep 2004 01:37:45 -0700 Subject: [PATCH] USB: remove calls to usb_unlink_urb in class/usb-midi.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/usb-midi.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/class/usb-midi.c b/drivers/usb/class/usb-midi.c index c4192ea52269..1dc952e9bc81 100644 --- a/drivers/usb/class/usb-midi.c +++ b/drivers/usb/class/usb-midi.c @@ -936,7 +936,7 @@ static int usb_midi_release(struct inode *inode, struct file *file) if ( m->open_mode & FMODE_WRITE ) { m->open_mode &= ~FMODE_WRITE; - usb_unlink_urb( m->mout.ep->urb ); + usb_kill_urb( m->mout.ep->urb ); } if ( m->open_mode & FMODE_READ ) { @@ -948,7 +948,7 @@ static int usb_midi_release(struct inode *inode, struct file *file) if ( m->min.ep->readers == 0 && m->min.ep->urbSubmitted ) { m->min.ep->urbSubmitted = 0; - usb_unlink_urb(m->min.ep->urb); + usb_kill_urb(m->min.ep->urb); } spin_unlock_irqrestore( &m->min.ep->lock, flagsep ); } @@ -1039,7 +1039,7 @@ static struct midi_in_endpoint *alloc_midi_in_endpoint( struct usb_device *d, in static int remove_midi_in_endpoint( struct midi_in_endpoint *min ) { - usb_unlink_urb( min->urb ); + usb_kill_urb( min->urb ); usb_free_urb( min->urb ); kfree( min->recvBuf ); kfree( min ); @@ -1099,7 +1099,7 @@ static struct midi_out_endpoint *alloc_midi_out_endpoint( struct usb_device *d, static int remove_midi_out_endpoint( struct midi_out_endpoint *mout ) { - usb_unlink_urb( mout->urb ); + usb_kill_urb( mout->urb ); usb_free_urb( mout->urb ); kfree( mout->buf ); kfree( mout ); -- cgit v1.2.3 From cbe7180db4bfce7bc2c34873799e3d49e74bed30 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Wed, 15 Sep 2004 01:38:17 -0700 Subject: [PATCH] USB: remove calls to usb_unlink_urb() in image/hpusbscsi.c here's my next cleanup. I've changed the name of the goto label in order to remain consistent with naming. Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/image/hpusbscsi.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/usb/image/hpusbscsi.c b/drivers/usb/image/hpusbscsi.c index dabc3666aee7..47a864b29f3d 100644 --- a/drivers/usb/image/hpusbscsi.c +++ b/drivers/usb/image/hpusbscsi.c @@ -106,7 +106,7 @@ hpusbscsi_usb_probe(struct usb_interface *intf, /* In host->hostdata we store a pointer to desc */ new->host = scsi_host_alloc(&hpusbscsi_scsi_host_template, sizeof(new)); if (!new->host) - goto out_unlink_controlurb; + goto out_kill_controlurb; new->host->hostdata[0] = (unsigned long)new; scsi_add_host(new->host, &intf->dev); /* XXX handle failure */ @@ -118,8 +118,8 @@ hpusbscsi_usb_probe(struct usb_interface *intf, usb_set_intfdata(intf, new); return 0; - out_unlink_controlurb: - usb_unlink_urb(new->controlurb); + out_kill_controlurb: + usb_kill_urb(new->controlurb); out_free_controlurb: usb_free_urb(new->controlurb); out_free_dataurb: @@ -137,7 +137,7 @@ hpusbscsi_usb_disconnect(struct usb_interface *intf) usb_set_intfdata(intf, NULL); scsi_remove_host(desc->host); - usb_unlink_urb(desc->controlurb); + usb_kill_urb(desc->controlurb); scsi_host_put(desc->host); usb_free_urb(desc->controlurb); @@ -280,8 +280,8 @@ static int hpusbscsi_scsi_abort (Scsi_Cmnd *srb) struct hpusbscsi* hpusbscsi = (struct hpusbscsi*)(srb->device->host->hostdata[0]); printk(KERN_DEBUG"Requested is canceled.\n"); - usb_unlink_urb(hpusbscsi->dataurb); - usb_unlink_urb(hpusbscsi->controlurb); + usb_kill_urb(hpusbscsi->dataurb); + usb_kill_urb(hpusbscsi->controlurb); hpusbscsi->state = HP_STATE_FREE; return SCSI_ABORT_PENDING; -- cgit v1.2.3 From 8bffe82eee0703a86f9119e65fa301190c849532 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Mon, 20 Sep 2004 20:33:57 -0700 Subject: [PATCH] USB: remove call to usb_unlink_urb() in media/usbvideo.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/media/usbvideo.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/usb/media/usbvideo.c b/drivers/usb/media/usbvideo.c index 1107e398d6f3..122b7accf1e1 100644 --- a/drivers/usb/media/usbvideo.c +++ b/drivers/usb/media/usbvideo.c @@ -1910,9 +1910,7 @@ static void usbvideo_StopDataPump(struct uvd *uvd) /* Unschedule all of the iso td's */ for (i=0; i < USBVIDEO_NUMSBUF; i++) { - j = usb_unlink_urb(uvd->sbuf[i].urb); - if (j < 0) - err("%s: usb_unlink_urb() error %d.", __FUNCTION__, j); + usb_kill_urb(uvd->sbuf[i].urb); } if (uvd->debug > 1) info("%s: streaming=0", __FUNCTION__); -- cgit v1.2.3 From d11aa2b83b8b23a408fb5e730e6389159d8408ac Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Mon, 20 Sep 2004 20:34:25 -0700 Subject: [PATCH] USB: remove calls to usb_unlink_urb in media/stv680.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/media/stv680.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/media/stv680.c b/drivers/usb/media/stv680.c index cd73a45d0dd0..a91870f0b412 100644 --- a/drivers/usb/media/stv680.c +++ b/drivers/usb/media/stv680.c @@ -725,7 +725,7 @@ static int stv680_stop_stream (struct usb_stv *stv680) for (i = 0; i < STV680_NUMSBUF; i++) if (stv680->urb[i]) { - usb_unlink_urb (stv680->urb[i]); + usb_kill_urb (stv680->urb[i]); usb_free_urb (stv680->urb[i]); stv680->urb[i] = NULL; kfree (stv680->sbuf[i].data); @@ -1457,7 +1457,7 @@ static inline void usb_stv680_remove_disconnected (struct usb_stv *stv680) for (i = 0; i < STV680_NUMSBUF; i++) if (stv680->urb[i]) { - usb_unlink_urb (stv680->urb[i]); + usb_kill_urb (stv680->urb[i]); usb_free_urb (stv680->urb[i]); stv680->urb[i] = NULL; kfree (stv680->sbuf[i].data); -- cgit v1.2.3 From 79e48216732db4d516ce520d18771584b5df8618 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Mon, 20 Sep 2004 20:35:01 -0700 Subject: [PATCH] USB: remove calls to usb_unlink_urb in media/se401.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/media/se401.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/media/se401.c b/drivers/usb/media/se401.c index c694caaac447..37d28640c790 100644 --- a/drivers/usb/media/se401.c +++ b/drivers/usb/media/se401.c @@ -514,7 +514,7 @@ static int se401_stop_stream(struct usb_se401 *se401) se401_sndctrl(1, se401, SE401_REQ_CAMERA_POWER, 0, NULL, 0); for (i=0; iurb[i]) { - usb_unlink_urb(se401->urb[i]); + usb_kill_urb(se401->urb[i]); usb_free_urb(se401->urb[i]); se401->urb[i]=NULL; kfree(se401->sbuf[i].data); @@ -883,7 +883,7 @@ static void usb_se401_remove_disconnected (struct usb_se401 *se401) se401->dev = NULL; for (i=0; iurb[i]) { - usb_unlink_urb(se401->urb[i]); + usb_kill_urb(se401->urb[i]); usb_free_urb(se401->urb[i]); se401->urb[i] = NULL; kfree(se401->sbuf[i].data); @@ -892,7 +892,7 @@ static void usb_se401_remove_disconnected (struct usb_se401 *se401) kfree(se401->scratch[i].data); } if (se401->inturb) { - usb_unlink_urb(se401->inturb); + usb_kill_urb(se401->inturb); usb_free_urb(se401->inturb); } info("%s disconnected", se401->camera_name); -- cgit v1.2.3 From ac04d3853809aceeaef9841eed9d90b119e1d089 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Mon, 20 Sep 2004 20:35:57 -0700 Subject: [PATCH] USB: remove call to usb_unlink_urb in media/ov511.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/media/ov511.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/media/ov511.c b/drivers/usb/media/ov511.c index 12f46de2bfe9..fe679ef2364b 100644 --- a/drivers/usb/media/ov511.c +++ b/drivers/usb/media/ov511.c @@ -3830,7 +3830,7 @@ ov51x_unlink_isoc(struct usb_ov511 *ov) /* Unschedule all of the iso td's */ for (n = OV511_NUMSBUF - 1; n >= 0; n--) { if (ov->sbuf[n].urb) { - usb_unlink_urb(ov->sbuf[n].urb); + usb_kill_urb(ov->sbuf[n].urb); usb_free_urb(ov->sbuf[n].urb); ov->sbuf[n].urb = NULL; } -- cgit v1.2.3 From 83990a4d054a5d3e16a92b4e5e300c6f0618b19f Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Mon, 20 Sep 2004 20:36:25 -0700 Subject: [PATCH] USB: remove calls to usb_unlink_urb in media/konicawc.c since usb_kill_urb is a void function, the checking of the return status had to go too. Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/media/konicawc.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/usb/media/konicawc.c b/drivers/usb/media/konicawc.c index 3376654ca051..aca3093c2b87 100644 --- a/drivers/usb/media/konicawc.c +++ b/drivers/usb/media/konicawc.c @@ -474,13 +474,8 @@ static void konicawc_stop_data(struct uvd *uvd) /* Unschedule all of the iso td's */ for (i=0; i < USBVIDEO_NUMSBUF; i++) { - j = usb_unlink_urb(uvd->sbuf[i].urb); - if (j < 0) - err("usb_unlink_urb() error %d.", j); - - j = usb_unlink_urb(cam->sts_urb[i]); - if (j < 0) - err("usb_unlink_urb() error %d.", j); + usb_kill_urb(uvd->sbuf[i].urb); + usb_kill_urb(cam->sts_urb[i]); } if (!uvd->remove_pending) { -- cgit v1.2.3 From 1dc47eb73b95024d20724dcc3925b02e8925fbb8 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Mon, 20 Sep 2004 20:36:51 -0700 Subject: [PATCH] USB: remove calls to usb_unlink_urb in input/xpad.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/input/xpad.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/input/xpad.c b/drivers/usb/input/xpad.c index 1956eb06b75c..c5fa87edb667 100644 --- a/drivers/usb/input/xpad.c +++ b/drivers/usb/input/xpad.c @@ -214,7 +214,7 @@ static void xpad_close (struct input_dev *dev) struct usb_xpad *xpad = dev->private; if (!--xpad->open_count) - usb_unlink_urb(xpad->irq_in); + usb_kill_urb(xpad->irq_in); } static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id) @@ -325,7 +325,7 @@ static void xpad_disconnect(struct usb_interface *intf) usb_set_intfdata(intf, NULL); if (xpad) { - usb_unlink_urb(xpad->irq_in); + usb_kill_urb(xpad->irq_in); input_unregister_device(&xpad->dev); usb_free_urb(xpad->irq_in); usb_buffer_free(interface_to_usbdev(intf), XPAD_PKT_LEN, xpad->idata, xpad->idata_dma); -- cgit v1.2.3 From d73c9a169190d882877ed0fdd00d511648d5b375 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Mon, 20 Sep 2004 20:37:20 -0700 Subject: [PATCH] USB: usb_unlink_urb removal from input/ati_remote.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/input/ati_remote.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/input/ati_remote.c b/drivers/usb/input/ati_remote.c index 61a42bdae1d8..3adbc7bab3c3 100644 --- a/drivers/usb/input/ati_remote.c +++ b/drivers/usb/input/ati_remote.c @@ -424,7 +424,7 @@ static int ati_remote_sendpacket(struct ati_remote *ati_remote, u16 cmd, unsigne set_current_state(TASK_RUNNING); remove_wait_queue(&ati_remote->wait, &wait); - usb_unlink_urb(ati_remote->out_urb); + usb_kill_urb(ati_remote->out_urb); return retval; } @@ -624,10 +624,10 @@ static void ati_remote_delete(struct ati_remote *ati_remote) if (!ati_remote) return; if (ati_remote->irq_urb) - usb_unlink_urb(ati_remote->irq_urb); + usb_kill_urb(ati_remote->irq_urb); if (ati_remote->out_urb) - usb_unlink_urb(ati_remote->out_urb); + usb_kill_urb(ati_remote->out_urb); input_unregister_device(&ati_remote->idev); -- cgit v1.2.3 From 0a52ea9f0611a911bdd31e48b35a3d0f4d876bcc Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Mon, 20 Sep 2004 20:37:49 -0700 Subject: [PATCH] USB: remove calls to usb_unlink_urb in input/wacom.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/input/wacom.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/input/wacom.c b/drivers/usb/input/wacom.c index c66001351165..b977c47a1af1 100644 --- a/drivers/usb/input/wacom.c +++ b/drivers/usb/input/wacom.c @@ -608,7 +608,7 @@ static void wacom_close(struct input_dev *dev) struct wacom *wacom = dev->private; if (!--wacom->open) - usb_unlink_urb(wacom->irq); + usb_kill_urb(wacom->irq); } static int wacom_probe(struct usb_interface *intf, const struct usb_device_id *id) @@ -729,7 +729,7 @@ static void wacom_disconnect(struct usb_interface *intf) usb_set_intfdata(intf, NULL); if (wacom) { - usb_unlink_urb(wacom->irq); + usb_kill_urb(wacom->irq); input_unregister_device(&wacom->dev); usb_free_urb(wacom->irq); usb_buffer_free(interface_to_usbdev(intf), 10, wacom->data, wacom->data_dma); -- cgit v1.2.3 From 1ec4eb3a1d0e5f0613596de3482cc18edb6e2506 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Mon, 20 Sep 2004 20:38:17 -0700 Subject: [PATCH] USB: remove calls to usb_unlink_urb in input/usbmouse.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/input/usbmouse.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/input/usbmouse.c b/drivers/usb/input/usbmouse.c index 5bf27306ea3c..8c7381b74499 100644 --- a/drivers/usb/input/usbmouse.c +++ b/drivers/usb/input/usbmouse.c @@ -118,7 +118,7 @@ static void usb_mouse_close(struct input_dev *dev) struct usb_mouse *mouse = dev->private; if (!--mouse->open) - usb_unlink_urb(mouse->irq); + usb_kill_urb(mouse->irq); } static int usb_mouse_probe(struct usb_interface * intf, const struct usb_device_id * id) @@ -223,7 +223,7 @@ static void usb_mouse_disconnect(struct usb_interface *intf) usb_set_intfdata(intf, NULL); if (mouse) { - usb_unlink_urb(mouse->irq); + usb_kill_urb(mouse->irq); input_unregister_device(&mouse->dev); usb_free_urb(mouse->irq); usb_buffer_free(interface_to_usbdev(intf), 8, mouse->data, mouse->data_dma); -- cgit v1.2.3 From 7689ae9c6eb774e6e23bdac5b960c070748676cd Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Mon, 20 Sep 2004 20:38:50 -0700 Subject: [PATCH] USB: remove calls to usb_unlink_urb in core/message.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/message.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index e20dde5d793a..3a10b1baf744 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -36,7 +36,7 @@ static void timeout_kill(unsigned long data) usb_pipecontrol(urb->pipe) ? "control" : "bulk", usb_pipeendpoint(urb->pipe), usb_pipein(urb->pipe) ? "in" : "out"); - usb_unlink_urb(urb); + usb_kill_urb(urb); } // Starts urb and waits for completion or timeout @@ -251,7 +251,7 @@ static void sg_complete (struct urb *urb, struct pt_regs *regs) if (!io->urbs [i] || !io->urbs [i]->dev) continue; if (found) { - status = usb_unlink_urb (io->urbs [i]); + status = usb_kill_urb (io->urbs [i]); if (status != -EINPROGRESS && status != -EBUSY) dev_err (&io->dev->dev, "%s, unlink --> %d\n", @@ -525,7 +525,7 @@ void usb_sg_cancel (struct usb_sg_request *io) if (!io->urbs [i]->dev) continue; - retval = usb_unlink_urb (io->urbs [i]); + retval = usb_kill_urb (io->urbs [i]); if (retval != -EINPROGRESS && retval != -EBUSY) dev_warn (&io->dev->dev, "%s, unlink --> %d\n", __FUNCTION__, retval); -- cgit v1.2.3 From 214ec5cb8bb7321420c258dbf5475f14ef026df6 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Mon, 20 Sep 2004 20:39:16 -0700 Subject: [PATCH] USB: remove usb_unlink_urb() calls in input/kbtab.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/input/kbtab.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/input/kbtab.c b/drivers/usb/input/kbtab.c index 7a29610e5218..bef4028741be 100644 --- a/drivers/usb/input/kbtab.c +++ b/drivers/usb/input/kbtab.c @@ -122,7 +122,7 @@ static void kbtab_close(struct input_dev *dev) struct kbtab *kbtab = dev->private; if (!--kbtab->open) - usb_unlink_urb(kbtab->irq); + usb_kill_urb(kbtab->irq); } static int kbtab_probe(struct usb_interface *intf, const struct usb_device_id *id) @@ -205,7 +205,7 @@ static void kbtab_disconnect(struct usb_interface *intf) usb_set_intfdata(intf, NULL); if (kbtab) { - usb_unlink_urb(kbtab->irq); + usb_kill_urb(kbtab->irq); input_unregister_device(&kbtab->dev); usb_free_urb(kbtab->irq); usb_buffer_free(interface_to_usbdev(intf), 10, kbtab->data, kbtab->data_dma); -- cgit v1.2.3 From 6bc76e9daf89515611b8be63223ebcd78186096e Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Mon, 20 Sep 2004 20:39:39 -0700 Subject: [PATCH] USB: usb_unlink_urb removal from input/hid-core.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/input/hid-core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/input/hid-core.c b/drivers/usb/input/hid-core.c index 34b0e91aeb1b..e57638d6af7a 100644 --- a/drivers/usb/input/hid-core.c +++ b/drivers/usb/input/hid-core.c @@ -1334,9 +1334,9 @@ void hid_init_reports(struct hid_device *hid) while (ret) { err |= ret; if (test_bit(HID_CTRL_RUNNING, &hid->iofl)) - usb_unlink_urb(hid->urbctrl); + usb_kill_urb(hid->urbctrl); if (test_bit(HID_OUT_RUNNING, &hid->iofl)) - usb_unlink_urb(hid->urbout); + usb_kill_urb(hid->urbout); ret = hid_wait_io(hid); } -- cgit v1.2.3 From c8de0f9bf77eb46d2a33174a945153e088e76b10 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Mon, 20 Sep 2004 20:40:11 -0700 Subject: [PATCH] USB: remove calls to usb_unlink_urb in input/mtouchusb.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/input/mtouchusb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/input/mtouchusb.c b/drivers/usb/input/mtouchusb.c index ae5713876d4c..9dcfd7e2ffbf 100644 --- a/drivers/usb/input/mtouchusb.c +++ b/drivers/usb/input/mtouchusb.c @@ -155,7 +155,7 @@ static void mtouchusb_close (struct input_dev *input) struct mtouch_usb *mtouch = input->private; if (!--mtouch->open) - usb_unlink_urb (mtouch->irq); + usb_kill_urb (mtouch->irq); } static int mtouchusb_alloc_buffers(struct usb_device *udev, struct mtouch_usb *mtouch) @@ -320,7 +320,7 @@ static void mtouchusb_disconnect(struct usb_interface *intf) usb_set_intfdata(intf, NULL); if (mtouch) { dbg("%s - mtouch is initialized, cleaning up", __FUNCTION__); - usb_unlink_urb(mtouch->irq); + usb_kill_urb(mtouch->irq); input_unregister_device(&mtouch->input); usb_free_urb(mtouch->irq); mtouchusb_free_buffers(interface_to_usbdev(intf), mtouch); -- cgit v1.2.3 From 1b00690cf6a638a4a2a00988d2a92158acd8e9a7 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Mon, 20 Sep 2004 20:40:39 -0700 Subject: [PATCH] USB: usb_unlink_urb removal from input/aiptek.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/input/aiptek.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/input/aiptek.c b/drivers/usb/input/aiptek.c index a97e2c5e2482..74c1754d9c21 100644 --- a/drivers/usb/input/aiptek.c +++ b/drivers/usb/input/aiptek.c @@ -837,7 +837,7 @@ static void aiptek_close(struct input_dev *inputdev) struct aiptek *aiptek = inputdev->private; if (--aiptek->openCount == 0) { - usb_unlink_urb(aiptek->urb); + usb_kill_urb(aiptek->urb); } } @@ -2258,7 +2258,7 @@ static void aiptek_disconnect(struct usb_interface *intf) if (aiptek != NULL) { /* Free & unhook everything from the system. */ - usb_unlink_urb(aiptek->urb); + usb_kill_urb(aiptek->urb); input_unregister_device(&aiptek->inputdev); aiptek_delete_files(&intf->dev); usb_free_urb(aiptek->urb); -- cgit v1.2.3 From b5df272b6abf2a070d4b2de3010977a432d2d5e7 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Mon, 20 Sep 2004 20:41:02 -0700 Subject: [PATCH] USB: remove calls to usb_unlink_urb in input/usbkbd.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/input/usbkbd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/input/usbkbd.c b/drivers/usb/input/usbkbd.c index 0c51b1eb84cf..1700f405b00b 100644 --- a/drivers/usb/input/usbkbd.c +++ b/drivers/usb/input/usbkbd.c @@ -196,7 +196,7 @@ static void usb_kbd_close(struct input_dev *dev) struct usb_kbd *kbd = dev->private; if (!--kbd->open) - usb_unlink_urb(kbd->irq); + usb_kill_urb(kbd->irq); } static int usb_kbd_alloc_mem(struct usb_device *dev, struct usb_kbd *kbd) @@ -343,7 +343,7 @@ static void usb_kbd_disconnect(struct usb_interface *intf) usb_set_intfdata(intf, NULL); if (kbd) { - usb_unlink_urb(kbd->irq); + usb_kill_urb(kbd->irq); input_unregister_device(&kbd->dev); usb_kbd_free_mem(interface_to_usbdev(intf), kbd); kfree(kbd); -- cgit v1.2.3 From efbaa5b3ed7b5752f5c06c9394ec4a7dea43c41f Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Mon, 20 Sep 2004 20:41:24 -0700 Subject: [PATCH] USB: remove calls to usb_unlink_urb() in input/pid.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/input/pid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/input/pid.c b/drivers/usb/input/pid.c index a38b0db8dcea..d1ea1f056599 100644 --- a/drivers/usb/input/pid.c +++ b/drivers/usb/input/pid.c @@ -56,7 +56,7 @@ static void hid_pid_exit(struct hid_device* hid) struct hid_ff_pid *private = hid->ff_private; if (private->urbffout) { - usb_unlink_urb(private->urbffout); + usb_kill_urb(private->urbffout); usb_free_urb(private->urbffout); } } -- cgit v1.2.3 From 4fcaaa7e8b92b684965fc6a207d03e65c94e59f4 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Mon, 20 Sep 2004 20:42:01 -0700 Subject: [PATCH] USB: remove calls to usb_unlink_urb in image/mdc800.c (v2) Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/image/mdc800.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/usb/image/mdc800.c b/drivers/usb/image/mdc800.c index f401557f0356..17e900c198cf 100644 --- a/drivers/usb/image/mdc800.c +++ b/drivers/usb/image/mdc800.c @@ -543,9 +543,9 @@ static void mdc800_usb_disconnect (struct usb_interface *intf) mdc800->state=NOT_CONNECTED; - usb_unlink_urb (mdc800->irq_urb); - usb_unlink_urb (mdc800->write_urb); - usb_unlink_urb (mdc800->download_urb); + usb_kill_urb(mdc800->irq_urb); + usb_kill_urb(mdc800->write_urb); + usb_kill_urb(mdc800->download_urb); mdc800->dev = NULL; usb_set_intfdata(intf, NULL); @@ -649,9 +649,9 @@ static int mdc800_device_release (struct inode* inode, struct file *file) down (&mdc800->io_lock); if (mdc800->open && (mdc800->state != NOT_CONNECTED)) { - usb_unlink_urb (mdc800->irq_urb); - usb_unlink_urb (mdc800->write_urb); - usb_unlink_urb (mdc800->download_urb); + usb_kill_urb(mdc800->irq_urb); + usb_kill_urb(mdc800->write_urb); + usb_kill_urb(mdc800->download_urb); mdc800->open=0; } else @@ -856,7 +856,7 @@ static ssize_t mdc800_device_write (struct file *file, const char __user *buf, s mdc800->written = 0; if (mdc800->state == WORKING) { - usb_unlink_urb (mdc800->write_urb); + usb_kill_urb(mdc800->write_urb); up (&mdc800->io_lock); return -EIO; } -- cgit v1.2.3 From 7158ba3acaecf1b920e352f15c5635119fb52891 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Mon, 20 Sep 2004 20:42:28 -0700 Subject: [PATCH] USB: remove calls to usb_unlink_urb in input/powermate.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/input/powermate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/input/powermate.c b/drivers/usb/input/powermate.c index 0ba1bcbfb431..852ebf8574a8 100644 --- a/drivers/usb/input/powermate.c +++ b/drivers/usb/input/powermate.c @@ -417,7 +417,7 @@ static void powermate_disconnect(struct usb_interface *intf) usb_set_intfdata(intf, NULL); if (pm) { pm->requires_update = 0; - usb_unlink_urb(pm->irq); + usb_kill_urb(pm->irq); input_unregister_device(&pm->input); usb_free_urb(pm->irq); usb_free_urb(pm->config); -- cgit v1.2.3 From 9e26f5920faf28aa8af8392fd5f58a51efbf2643 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Mon, 20 Sep 2004 20:42:57 -0700 Subject: [PATCH] USB: remove calls to usb_unlink_urb() in input/touchkitusb.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/input/touchkitusb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/input/touchkitusb.c b/drivers/usb/input/touchkitusb.c index 4917b042ef9a..14443f926ab0 100644 --- a/drivers/usb/input/touchkitusb.c +++ b/drivers/usb/input/touchkitusb.c @@ -141,7 +141,7 @@ static void touchkit_close(struct input_dev *input) struct touchkit_usb *touchkit = input->private; if (!--touchkit->open) - usb_unlink_urb(touchkit->irq); + usb_kill_urb(touchkit->irq); } static int touchkit_alloc_buffers(struct usb_device *udev, @@ -276,7 +276,7 @@ static void touchkit_disconnect(struct usb_interface *intf) dbg("%s - touchkit is initialized, cleaning up", __FUNCTION__); usb_set_intfdata(intf, NULL); input_unregister_device(&touchkit->input); - usb_unlink_urb(touchkit->irq); + usb_kill_urb(touchkit->irq); usb_free_urb(touchkit->irq); touchkit_free_buffers(interface_to_usbdev(intf), touchkit); kfree(touchkit); -- cgit v1.2.3 From 90578145020a53b84d0cc6850b042cff939aa8e7 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Mon, 20 Sep 2004 21:16:22 -0700 Subject: [PATCH] USB: oops, revert drivers/usb/core/message.c change. That patch was just not right... Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/message.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 3a10b1baf744..e20dde5d793a 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -36,7 +36,7 @@ static void timeout_kill(unsigned long data) usb_pipecontrol(urb->pipe) ? "control" : "bulk", usb_pipeendpoint(urb->pipe), usb_pipein(urb->pipe) ? "in" : "out"); - usb_kill_urb(urb); + usb_unlink_urb(urb); } // Starts urb and waits for completion or timeout @@ -251,7 +251,7 @@ static void sg_complete (struct urb *urb, struct pt_regs *regs) if (!io->urbs [i] || !io->urbs [i]->dev) continue; if (found) { - status = usb_kill_urb (io->urbs [i]); + status = usb_unlink_urb (io->urbs [i]); if (status != -EINPROGRESS && status != -EBUSY) dev_err (&io->dev->dev, "%s, unlink --> %d\n", @@ -525,7 +525,7 @@ void usb_sg_cancel (struct usb_sg_request *io) if (!io->urbs [i]->dev) continue; - retval = usb_kill_urb (io->urbs [i]); + retval = usb_unlink_urb (io->urbs [i]); if (retval != -EINPROGRESS && retval != -EBUSY) dev_warn (&io->dev->dev, "%s, unlink --> %d\n", __FUNCTION__, retval); -- cgit v1.2.3 From a0e64cc0ae584bbf8c3e8637cec61289f17bf1ff Mon Sep 17 00:00:00 2001 From: Duncan Sands Date: Mon, 20 Sep 2004 21:17:31 -0700 Subject: [PATCH] usb speedtch: no side-effects in BUG_ON It seems that people want BUG_ON to be like an assertion: harmless if removed. Signed-off-by: Duncan Sands Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/speedtch.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/usb/misc/speedtch.c b/drivers/usb/misc/speedtch.c index 667e2d1b227c..9e191e0a2837 100644 --- a/drivers/usb/misc/speedtch.c +++ b/drivers/usb/misc/speedtch.c @@ -93,7 +93,7 @@ #ifdef DEBUG #define DEBUG_ON(x) BUG_ON(x) #else -#define DEBUG_ON(x) do { if (x); } while (0) +#define DEBUG_ON(x) #endif #ifdef VERBOSE_DEBUG @@ -518,7 +518,8 @@ static unsigned int udsl_write_cells (unsigned int howmany, struct sk_buff *skb, memset (target, 0, ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER); target += ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER; - DEBUG_ON (--ctrl->num_cells); + --ctrl->num_cells; + DEBUG_ON (ctrl->num_cells); } memcpy (target, ctrl->aal5_trailer, ATM_AAL5_TRAILER); @@ -1243,8 +1244,10 @@ static void udsl_usb_disconnect (struct usb_interface *intf) do { count = 0; spin_lock_irq (&instance->receive_lock); - list_for_each (pos, &instance->spare_receivers) - DEBUG_ON (++count > num_rcv_urbs); + list_for_each (pos, &instance->spare_receivers) { + ++count; + DEBUG_ON (count > num_rcv_urbs); + } spin_unlock_irq (&instance->receive_lock); dbg ("udsl_usb_disconnect: found %u spare receivers", count); @@ -1279,8 +1282,10 @@ static void udsl_usb_disconnect (struct usb_interface *intf) do { count = 0; spin_lock_irq (&instance->send_lock); - list_for_each (pos, &instance->spare_senders) - DEBUG_ON (++count > num_snd_urbs); + list_for_each (pos, &instance->spare_senders) { + ++count; + DEBUG_ON (count > num_snd_urbs); + } spin_unlock_irq (&instance->send_lock); dbg ("udsl_usb_disconnect: found %u spare senders", count); -- cgit v1.2.3 From 43e6b5b421cd9b09282f0c4aa9b49e1231826e22 Mon Sep 17 00:00:00 2001 From: Duncan Sands Date: Mon, 20 Sep 2004 21:18:43 -0700 Subject: [PATCH] usb speedtch: convert to using usb_kill_urb Unlike usb_unlink_urb, usb_kill_urb guarantees that completion handlers have finished running before it returns. The bunch of disconnect code that waited for completion handlers can now go away. Signed-off-by: Duncan Sands Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/speedtch.c | 48 +++------------------------------------------ 1 file changed, 3 insertions(+), 45 deletions(-) diff --git a/drivers/usb/misc/speedtch.c b/drivers/usb/misc/speedtch.c index 9e191e0a2837..c8b29eef1c7d 100644 --- a/drivers/usb/misc/speedtch.c +++ b/drivers/usb/misc/speedtch.c @@ -1220,9 +1220,7 @@ fail: static void udsl_usb_disconnect (struct usb_interface *intf) { struct udsl_instance_data *instance = usb_get_intfdata (intf); - struct list_head *pos; - unsigned int count; - int result, i; + int i; dbg ("udsl_usb_disconnect entered"); @@ -1237,27 +1235,7 @@ static void udsl_usb_disconnect (struct usb_interface *intf) tasklet_disable (&instance->receive_tasklet); for (i = 0; i < num_rcv_urbs; i++) - if ((result = usb_unlink_urb (instance->receivers [i].urb)) < 0) - dbg ("udsl_usb_disconnect: usb_unlink_urb on receive urb %d returned %d!", i, result); - - /* wait for completion handlers to finish */ - do { - count = 0; - spin_lock_irq (&instance->receive_lock); - list_for_each (pos, &instance->spare_receivers) { - ++count; - DEBUG_ON (count > num_rcv_urbs); - } - spin_unlock_irq (&instance->receive_lock); - - dbg ("udsl_usb_disconnect: found %u spare receivers", count); - - if (count == num_rcv_urbs) - break; - - set_current_state (TASK_RUNNING); - schedule (); - } while (1); + usb_kill_urb (instance->receivers [i].urb); /* no need to take the spinlock */ INIT_LIST_HEAD (&instance->filled_receive_buffers); @@ -1275,27 +1253,7 @@ static void udsl_usb_disconnect (struct usb_interface *intf) tasklet_disable (&instance->send_tasklet); for (i = 0; i < num_snd_urbs; i++) - if ((result = usb_unlink_urb (instance->senders [i].urb)) < 0) - dbg ("udsl_usb_disconnect: usb_unlink_urb on send urb %d returned %d!", i, result); - - /* wait for completion handlers to finish */ - do { - count = 0; - spin_lock_irq (&instance->send_lock); - list_for_each (pos, &instance->spare_senders) { - ++count; - DEBUG_ON (count > num_snd_urbs); - } - spin_unlock_irq (&instance->send_lock); - - dbg ("udsl_usb_disconnect: found %u spare senders", count); - - if (count == num_snd_urbs) - break; - - set_current_state (TASK_RUNNING); - schedule (); - } while (1); + usb_kill_urb (instance->senders [i].urb); /* no need to take the spinlock */ INIT_LIST_HEAD (&instance->spare_senders); -- cgit v1.2.3 From 77022ae334b1083915eae525acf72ea4931777c9 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 21 Sep 2004 00:41:16 -0700 Subject: USB: fix incorrect usage of usb_kill_urb in rtl8150 driver. Signed-off-by: Greg Kroah-Hartman --- drivers/usb/net/rtl8150.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/net/rtl8150.c b/drivers/usb/net/rtl8150.c index 4caad655aeb0..c8190731fd06 100644 --- a/drivers/usb/net/rtl8150.c +++ b/drivers/usb/net/rtl8150.c @@ -663,7 +663,7 @@ static void rtl8150_tx_timeout(struct net_device *netdev) return; warn("%s: Tx timeout.", netdev->name); dev->tx_urb->transfer_flags |= URB_ASYNC_UNLINK; - usb_kill_urb(dev->tx_urb); + usb_unlink_urb(dev->tx_urb); dev->stats.tx_errors++; } -- cgit v1.2.3 From 811c19c25076a4f68fc10570082f6bdc15d93290 Mon Sep 17 00:00:00 2001 From: Vojtech Pavlik Date: Tue, 21 Sep 2004 01:56:32 -0700 Subject: [PATCH] USB: Fix oops in usblp driver This bug was reported back in July, and I sent out a patch but apparently it never got to you. The usblp driver was calling usb_buffer_free() from usblp_cleanup(), which runs after disconnect() if a user process holds the device open. But once the usb_device is gone usb_buffer_free() will oops. The patch frees the buffers in usb_disconnect() instead. Recently Joost Witteveen reported the same oops and found that the patch solved it for him. So there shouldn't be problems with accepting it. Signed-off-by: Alan Stern Signed-off-by: Vojtech Pavlik Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/usblp.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/class/usblp.c b/drivers/usb/class/usblp.c index 3174bf78fefc..2b9a42bbd32b 100644 --- a/drivers/usb/class/usblp.c +++ b/drivers/usb/class/usblp.c @@ -397,10 +397,6 @@ static void usblp_cleanup (struct usblp *usblp) { info("usblp%d: removed", usblp->minor); - usb_buffer_free (usblp->dev, USBLP_BUF_SIZE, - usblp->writebuf, usblp->writeurb->transfer_dma); - usb_buffer_free (usblp->dev, USBLP_BUF_SIZE, - usblp->readbuf, usblp->readurb->transfer_dma); kfree (usblp->device_id_string); kfree (usblp->statusbuf); usb_free_urb(usblp->writeurb); @@ -1159,6 +1155,10 @@ static void usblp_disconnect(struct usb_interface *intf) usb_set_intfdata (intf, NULL); usblp_unlink_urbs(usblp); + usb_buffer_free (usblp->dev, USBLP_BUF_SIZE, + usblp->writebuf, usblp->writeurb->transfer_dma); + usb_buffer_free (usblp->dev, USBLP_BUF_SIZE, + usblp->readbuf, usblp->readurb->transfer_dma); if (!usblp->used) usblp_cleanup (usblp); -- cgit v1.2.3 From 8f895a819d542b33909b433229db3a5d192fa34c Mon Sep 17 00:00:00 2001 From: David Brownell Date: Tue, 21 Sep 2004 01:57:04 -0700 Subject: [PATCH] USB: net2280 updates Net2280 patches to: - Make reset logic ensure endpoint toggle and halt bits only get cleared on endpoints other than ep0 to fix an extremely unlikely (but possible) state when a setup packet come in after we've checked the reset status but before calling ep_reset(). - Avoid a disconnect hang by exchanging SUSPEND_IMMEDIATELY (intended for use when the 8051 is the PCI host) with SUSPEND_REQUEST_INTERRUPT to suspend the NET2280. - Make rmmod of gadget drivers trigger disconnect; earlier changes to reset logic broke this. Signed-off-by: Alex Sanks Signed-off-by: David Brownell Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/net2280.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/usb/gadget/net2280.c b/drivers/usb/gadget/net2280.c index 5b61f9e49959..9148f7ed4701 100644 --- a/drivers/usb/gadget/net2280.c +++ b/drivers/usb/gadget/net2280.c @@ -303,13 +303,16 @@ static void ep_reset (struct net2280_regs *regs, struct net2280_ep *ep) /* init to our chosen defaults, notably so that we NAK OUT * packets until the driver queues a read (+note erratum 0112) */ - writel ( (1 << SET_NAK_OUT_PACKETS_MODE) + tmp = (1 << SET_NAK_OUT_PACKETS_MODE) | (1 << SET_NAK_OUT_PACKETS) | (1 << CLEAR_EP_HIDE_STATUS_PHASE) - | (1 << CLEAR_INTERRUPT_MODE) - | (1 << CLEAR_ENDPOINT_TOGGLE) - | (1 << CLEAR_ENDPOINT_HALT) - , &ep->regs->ep_rsp); + | (1 << CLEAR_INTERRUPT_MODE); + + if (ep->num != 0) { + tmp |= (1 << CLEAR_ENDPOINT_TOGGLE) + | (1 << CLEAR_ENDPOINT_HALT); + } + writel (tmp, &ep->regs->ep_rsp); /* scrub most status bits, and flush any fifo state */ writel ( (1 << TIMEOUT) @@ -1919,8 +1922,6 @@ static void ep0_start (struct net2280 *dev) , &dev->usb->stdrsp); writel ( (1 << USB_ROOT_PORT_WAKEUP_ENABLE) | (1 << SELF_POWERED_USB_DEVICE) - /* erratum 0102 workaround */ - | ((dev->chiprev == 0100) ? 0 : 1) << SUSPEND_IMMEDIATELY | (1 << REMOTE_WAKEUP_SUPPORT) | (dev->softconnect << USB_DETECT_ENABLE) | (1 << SELF_POWERED_STATUS) @@ -2046,6 +2047,8 @@ int usb_gadget_unregister_driver (struct usb_gadget_driver *driver) stop_activity (dev, driver); spin_unlock_irqrestore (&dev->lock, flags); + net2280_pullup (&dev->gadget, 0); + driver->unbind (&dev->gadget); dev->gadget.dev.driver = NULL; dev->driver = NULL; @@ -2551,8 +2554,6 @@ static void handle_stat1_irqs (struct net2280 *dev, u32 stat) if (stat & (1 << SUSPEND_REQUEST_INTERRUPT)) { if (dev->driver->suspend) dev->driver->suspend (&dev->gadget); - /* we use SUSPEND_IMMEDIATELY */ - stat &= ~(1 << SUSPEND_REQUEST_INTERRUPT); } else { if (dev->driver->resume) dev->driver->resume (&dev->gadget); -- cgit v1.2.3 From cf8b58a59527b2b35f0a6db5c5e3c84185356259 Mon Sep 17 00:00:00 2001 From: David Brownell Date: Tue, 21 Sep 2004 01:57:33 -0700 Subject: [PATCH] USB: host side fixes for pxa2xx/ethernet/rndis gadgets, like gumstix This fixes an interop glitch with PXA gadgets; please merge. This resolves an issue that's more or less specific to hosts trying to talk pxa255 based Linux devices using the Ethernet/RNDIS gadget driver. - Teaches "usbnet" about the product ID used by pxa255 based devices when they enable RNDIS, since it won't be using CDC Ethernet. - Forces usbcore config selection code to use non-RNDIS configurations, even when their class is vendor-specific (as in the pxa255 case). This makes gumstix devices, for example, talk with Linux 2.6.9 hosts AND with Windows ... previously only one would work. From: Craig Hughes Signed-off-by: David Brownell Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 12 ++++++++++-- drivers/usb/net/usbnet.c | 13 ++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index c1ba248b6aac..7090e4497b37 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -1045,11 +1045,19 @@ static int choose_configuration(struct usb_device *udev) ->altsetting->desc; if (desc->bInterfaceClass == USB_CLASS_VENDOR_SPEC) continue; - /* COMM/2/all is CDC ACM, except 0xff is MSFT RNDIS */ + /* COMM/2/all is CDC ACM, except 0xff is MSFT RNDIS. + * MSFT needs this to be the first config; never use + * it as the default unless Linux has host-side RNDIS. + * A second config would ideally be CDC-Ethernet, but + * may instead be the "vendor specific" CDC subset + * long used by ARM Linux for sa1100 or pxa255. + */ if (desc->bInterfaceClass == USB_CLASS_COMM && desc->bInterfaceSubClass == 2 - && desc->bInterfaceProtocol == 0xff) + && desc->bInterfaceProtocol == 0xff) { + c = udev->config[1].desc.bConfigurationValue; continue; + } c = udev->config[i].desc.bConfigurationValue; break; } diff --git a/drivers/usb/net/usbnet.c b/drivers/usb/net/usbnet.c index 67b2b2c12647..0ca25c357e5b 100644 --- a/drivers/usb/net/usbnet.c +++ b/drivers/usb/net/usbnet.c @@ -3308,11 +3308,18 @@ static const struct usb_device_id products [] = { * * PXA25x or PXA210 ... these use a "usb-eth" driver much like * the sa1100 one, but hardware uses different endpoint numbers. + * + * Or the Linux "Ethernet" gadget on hardware that can't talk + * CDC Ethernet (e.g., no altsettings), in either of two modes: + * - acting just like the old "usb-eth" firmware, though + * the implementation is different + * - supporting RNDIS as the first/default configuration for + * MS-Windows interop; Linux needs to use the other config */ { // 1183 = 0x049F, both used as hex values? // Compaq "Itsy" vendor/product id - USB_DEVICE (0x049F, 0x505A), + USB_DEVICE (0x049F, 0x505A), // usb-eth, or compatible .driver_info = (unsigned long) &linuxdev_info, }, { USB_DEVICE (0x0E7E, 0x1001), // G.Mate "Yopy" @@ -3320,6 +3327,10 @@ static const struct usb_device_id products [] = { }, { USB_DEVICE (0x8086, 0x07d3), // "blob" bootloader .driver_info = (unsigned long) &blob_info, +}, { + // Linux Ethernet/RNDIS gadget on pxa210/25x/26x + USB_DEVICE_VER (0x0525, 0xa4a2, 0x0203, 0x0203), + .driver_info = (unsigned long) &linuxdev_info, }, #endif -- cgit v1.2.3 From 2a7b57bd9490ab88ff1997d99d517d3254652f7d Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 21 Sep 2004 01:58:11 -0700 Subject: [PATCH] USB: Support system suspend in File-Storage Gadget This patch adds support for system-wide suspend to the File-Storage Gadget. Please apply. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/file_storage.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/gadget/file_storage.c b/drivers/usb/gadget/file_storage.c index 6987b7c3e7c3..c0f131f5e235 100644 --- a/drivers/usb/gadget/file_storage.c +++ b/drivers/usb/gadget/file_storage.c @@ -234,6 +234,7 @@ #include #include #include +#include #include #include #include @@ -1540,6 +1541,8 @@ static int sleep_thread(struct fsg_dev *fsg) rc = wait_event_interruptible(fsg->thread_wqh, fsg->thread_wakeup_needed); fsg->thread_wakeup_needed = 0; + if (current->flags & PF_FREEZE) + refrigerator(PF_FREEZE); return (rc ? -EINTR : 0); } -- cgit v1.2.3 From d021dd823f451f18dd752b1b4fbbcae7e88e76cc Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 21 Sep 2004 01:58:41 -0700 Subject: [PATCH] USB: Suspend update for dummy_hcd This patch fixes a few minor errors in the port suspend/resume handling code of the dummy_hcd driver. There are a couple of other small changes too, such as removing a BUG_ON(). Please apply. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/dummy_hcd.c | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/drivers/usb/gadget/dummy_hcd.c b/drivers/usb/gadget/dummy_hcd.c index 91e878642b9f..ed532f7176f9 100644 --- a/drivers/usb/gadget/dummy_hcd.c +++ b/drivers/usb/gadget/dummy_hcd.c @@ -770,7 +770,8 @@ usb_gadget_unregister_driver (struct usb_gadget_driver *driver) spin_lock_irqsave (&dum->lock, flags); stop_activity (dum, driver); - dum->port_status &= ~USB_PORT_STAT_CONNECTION; + dum->port_status &= ~(USB_PORT_STAT_CONNECTION | USB_PORT_STAT_ENABLE | + USB_PORT_STAT_LOW_SPEED | USB_PORT_STAT_HIGH_SPEED); dum->port_status |= (1 << USB_PORT_FEAT_C_CONNECTION); spin_unlock_irqrestore (&dum->lock, flags); @@ -815,8 +816,8 @@ static int dummy_urb_enqueue ( struct dummy *dum; unsigned long flags; - /* patch to usb_sg_init() is in 2.5.60 */ - BUG_ON (!urb->transfer_buffer && urb->transfer_buffer_length); + if (!urb->transfer_buffer && urb->transfer_buffer_length) + return -EINVAL; dum = container_of (hcd, struct dummy, hcd); spin_lock_irqsave (&dum->lock, flags); @@ -1102,10 +1103,10 @@ restart: ep = find_endpoint(dum, address); if (!ep) { /* set_configuration() disagreement */ - dev_err (hardware, + dev_dbg (hardware, "no ep configured for urb %p\n", urb); - maybe_set_status (urb, -ETIMEDOUT); + maybe_set_status (urb, -EPROTO); goto return_urb; } @@ -1409,9 +1410,12 @@ static int dummy_hub_control ( case ClearPortFeature: switch (wValue) { case USB_PORT_FEAT_SUSPEND: - /* 20msec resume signaling */ - dum->resuming = 1; - dum->re_timeout = jiffies + ((HZ * 20)/1000); + if (dum->port_status & (1 << USB_PORT_FEAT_SUSPEND)) { + /* 20msec resume signaling */ + dum->resuming = 1; + dum->re_timeout = jiffies + + msecs_to_jiffies(20); + } break; case USB_PORT_FEAT_POWER: dum->port_status = 0; @@ -1440,7 +1444,7 @@ static int dummy_hub_control ( dum->port_status &= ~(1 << USB_PORT_FEAT_SUSPEND); dum->resuming = 0; dum->re_timeout = 0; - if (dum->driver->resume) { + if (dum->driver && dum->driver->resume) { spin_unlock (&dum->lock); dum->driver->resume (&dum->gadget); spin_lock (&dum->lock); @@ -1481,11 +1485,15 @@ static int dummy_hub_control ( case SetPortFeature: switch (wValue) { case USB_PORT_FEAT_SUSPEND: - dum->port_status |= (1 << USB_PORT_FEAT_SUSPEND); - if (dum->driver->suspend) { - spin_unlock (&dum->lock); - dum->driver->suspend (&dum->gadget); - spin_lock (&dum->lock); + if ((dum->port_status & (1 << USB_PORT_FEAT_SUSPEND)) + == 0) { + dum->port_status |= + (1 << USB_PORT_FEAT_SUSPEND); + if (dum->driver && dum->driver->suspend) { + spin_unlock (&dum->lock); + dum->driver->suspend (&dum->gadget); + spin_lock (&dum->lock); + } } break; case USB_PORT_FEAT_RESET: @@ -1502,7 +1510,7 @@ static int dummy_hub_control ( /* FIXME test that code path! */ } /* 50msec reset signaling */ - dum->re_timeout = jiffies + ((HZ * 50)/1000); + dum->re_timeout = jiffies + msecs_to_jiffies(50); /* FALLTHROUGH */ default: dum->port_status |= (1 << wValue); @@ -1790,4 +1798,3 @@ static void __exit cleanup (void) the_controller = 0; } module_exit (cleanup); - -- cgit v1.2.3 From baf4a4c3a6b857b34f67708078507716e144eae1 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 21 Sep 2004 01:59:23 -0700 Subject: [PATCH] USB: Activate new hubs and resumed hubs the same way This patch makes the newly-probed hub pathway, the hub reset pathway, and the resumed hub pathway all use the same routine for activating the hub and scanning for port change events as soon as possible. It's a modified version of something originally written by David Brownell, updated to match the current source. Please apply. Signed-off-by: David Brownell Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 76 ++++++++++++++++++++++---------------------------- 1 file changed, 33 insertions(+), 43 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 7090e4497b37..10baa8f52566 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -395,6 +395,33 @@ static void hub_power_on(struct usb_hub *hub) msleep(hub->descriptor->bPwrOn2PwrGood * 2); } +static void hub_quiesce(struct usb_hub *hub) +{ + /* stop khubd and related activity */ + hub->quiescing = 1; + usb_kill_urb(hub->urb); + if (hub->has_indicators) + cancel_delayed_work(&hub->leds); + if (hub->has_indicators || hub->tt.hub) + flush_scheduled_work(); +} + +static void hub_activate(struct usb_hub *hub) +{ + int status; + + hub->quiescing = 0; + status = usb_submit_urb(hub->urb, GFP_NOIO); + if (status < 0) + dev_err(&hub->intf->dev, "activate --> %d\n", status); + if (hub->has_indicators && blinkenlights) + schedule_delayed_work(&hub->leds, LED_CYCLE_PERIOD); + + /* scan all ports ASAP */ + hub->event_bits[0] = ~0; + kick_khubd(hub); +} + static int hub_hub_status(struct usb_hub *hub, u16 *status, u16 *change) { @@ -590,10 +617,7 @@ static int hub_configure(struct usb_hub *hub, dev_dbg(hub_dev, "%sover-current condition exists\n", (hubstatus & HUB_STATUS_OVERCURRENT) ? "" : "no "); - /* scan all ports ASAP on new hubs */ - hub->change_bits[0] = ~0; - - /* Start the interrupt endpoint */ + /* set up the interrupt endpoint */ pipe = usb_rcvintpipe(hdev, endpoint->bEndpointAddress); maxp = usb_maxpacket(hdev, pipe, usb_pipeout(pipe)); @@ -611,24 +635,13 @@ static int hub_configure(struct usb_hub *hub, hub, endpoint->bInterval); hub->urb->transfer_dma = hub->buffer_dma; hub->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; - ret = usb_submit_urb(hub->urb, GFP_KERNEL); - if (ret) { - message = "couldn't submit status urb"; - goto fail; - } - /* Wake up khubd */ - kick_khubd(hub); - - /* maybe start cycling the hub leds */ - if (hub->has_indicators && blinkenlights) { - set_port_led(hdev, 1, HUB_LED_GREEN); + /* maybe cycle the hub leds */ + if (hub->has_indicators && blinkenlights) hub->indicator [0] = INDICATOR_CYCLE; - schedule_delayed_work(&hub->leds, LED_CYCLE_PERIOD); - } hub_power_on(hub); - + hub_activate(hub); return 0; fail: @@ -640,29 +653,6 @@ fail: static unsigned highspeed_hubs; -static void hub_quiesce(struct usb_hub *hub) -{ - /* stop khubd and related activity */ - hub->quiescing = 1; - usb_kill_urb(hub->urb); - if (hub->has_indicators) - cancel_delayed_work(&hub->leds); - if (hub->has_indicators || hub->tt.hub) - flush_scheduled_work(); -} - -static void hub_reactivate(struct usb_hub *hub) -{ - int status; - - hub->quiescing = 0; - status = usb_submit_urb(hub->urb, GFP_NOIO); - if (status < 0) - dev_err(&hub->intf->dev, "reactivate --> %d\n", status); - if (hub->has_indicators && blinkenlights) - schedule_delayed_work(&hub->leds, LED_CYCLE_PERIOD); -} - static void hub_disconnect(struct usb_interface *intf) { struct usb_hub *hub = usb_get_intfdata (intf); @@ -822,7 +812,7 @@ static void hub_post_reset(struct usb_device *hdev) { struct usb_hub *hub = usb_get_intfdata(hdev->actconfig->interface[0]); - hub_reactivate(hub); + hub_activate(hub); hub_power_on(hub); } @@ -1905,7 +1895,7 @@ static int hub_resume(struct usb_interface *intf) } intf->dev.power.power_state = PM_SUSPEND_ON; - hub_reactivate(hub); + hub_activate(hub); return 0; } -- cgit v1.2.3 From a2ea17cb132468fabad09b94f78662000fb937c6 Mon Sep 17 00:00:00 2001 From: "David T. Hollis" Date: Tue, 21 Sep 2004 01:59:57 -0700 Subject: [PATCH] USB: Add Surecom USB Ethernet device ids to usbnet Add Surecom EP-1427X-2 device ids. Signed-off-by: David Hollis Signed-off-by: Greg Kroah-Hartman --- drivers/usb/net/usbnet.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/net/usbnet.c b/drivers/usb/net/usbnet.c index 0ca25c357e5b..380cc5db14d4 100644 --- a/drivers/usb/net/usbnet.c +++ b/drivers/usb/net/usbnet.c @@ -3252,6 +3252,10 @@ static const struct usb_device_id products [] = { // Sitecom LN-029 "USB 2.0 10/100 Ethernet adapter" USB_DEVICE (0x6189, 0x182d), .driver_info = (unsigned long) &ax8817x_info, +}, { + // Surecom EP-1427X-2 + USB_DEVICE (0x1189, 0x0893), + .driver_info = (unsigned long) &ax8817x_info, }, #endif -- cgit v1.2.3 From 0a5b752b9d2dd13041ae008abe2d8fa6cd75fbb7 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 21 Sep 2004 02:00:26 -0700 Subject: [PATCH] USB: Use list_for_each_entry etc. in UHCI driver This patch was developed largely by Domen Puncer. It makes the code in the UHCI driver more readable by utilizing list_for_each_entry and related macros. I merely made a couple of very small stylistic changes and converted one additional hunk of code. Signed-off-by: Domen Puncer Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/uhci-hcd.c | 237 ++++++++++---------------------------------- 1 file changed, 52 insertions(+), 185 deletions(-) diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c index fe924e65ba35..07076731937f 100644 --- a/drivers/usb/host/uhci-hcd.c +++ b/drivers/usb/host/uhci-hcd.c @@ -230,42 +230,22 @@ static void uhci_remove_td(struct uhci_hcd *uhci, struct uhci_td *td) } /* - * Inserts a td into qh list at the top. + * Inserts a td list into qh. */ static void uhci_insert_tds_in_qh(struct uhci_qh *qh, struct urb *urb, u32 breadth) { - struct list_head *tmp, *head; struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; - struct uhci_td *td, *ptd; - - if (list_empty(&urbp->td_list)) - return; - - head = &urbp->td_list; - tmp = head->next; + struct uhci_td *td; + u32 *plink; /* Ordering isn't important here yet since the QH hasn't been */ - /* inserted into the schedule yet */ - td = list_entry(tmp, struct uhci_td, list); - - /* Add the first TD to the QH element pointer */ - qh->element = cpu_to_le32(td->dma_handle) | breadth; - - ptd = td; - - /* Then link the rest of the TD's */ - tmp = tmp->next; - while (tmp != head) { - td = list_entry(tmp, struct uhci_td, list); - - tmp = tmp->next; - - ptd->link = cpu_to_le32(td->dma_handle) | breadth; - - ptd = td; + /* inserted into the schedule yet */ + plink = &qh->element; + list_for_each_entry(td, &urbp->td_list, list) { + *plink = cpu_to_le32(td->dma_handle) | breadth; + plink = &td->link; } - - ptd->link = UHCI_PTR_TERM; + *plink = UHCI_PTR_TERM; } static void uhci_free_td(struct uhci_hcd *uhci, struct uhci_td *td) @@ -330,7 +310,7 @@ static void uhci_free_qh(struct uhci_hcd *uhci, struct uhci_qh *qh) static void uhci_insert_qh(struct uhci_hcd *uhci, struct uhci_qh *skelqh, struct urb *urb) { struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; - struct list_head *tmp; + struct urb_priv *turbp; struct uhci_qh *lqh; /* Grab the last QH */ @@ -358,12 +338,8 @@ static void uhci_insert_qh(struct uhci_hcd *uhci, struct uhci_qh *skelqh, struct */ lqh->link = cpu_to_le32(urbp->qh->dma_handle) | UHCI_PTR_QH; if (lqh->urbp) { - list_for_each (tmp, &lqh->urbp->queue_list) { - struct urb_priv *turbp = - list_entry(tmp, struct urb_priv, queue_list); - + list_for_each_entry(turbp, &lqh->urbp->queue_list, queue_list) turbp->qh->link = lqh->link; - } } list_add_tail(&urbp->qh->list, &skelqh->list); @@ -405,18 +381,11 @@ static void uhci_remove_qh(struct uhci_hcd *uhci, struct uhci_qh *qh) pqh = list_entry(qh->list.prev, struct uhci_qh, list); pqh->link = newlink; if (pqh->urbp) { - struct list_head *head, *tmp; - - head = &pqh->urbp->queue_list; - tmp = head->next; - while (head != tmp) { - struct urb_priv *turbp = - list_entry(tmp, struct urb_priv, queue_list); - - tmp = tmp->next; + struct urb_priv *turbp; + list_for_each_entry(turbp, &pqh->urbp->queue_list, + queue_list) turbp->qh->link = newlink; - } } wmb(); @@ -447,21 +416,14 @@ static void uhci_remove_qh(struct uhci_hcd *uhci, struct uhci_qh *qh) static int uhci_fixup_toggle(struct urb *urb, unsigned int toggle) { struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; - struct list_head *head, *tmp; - - head = &urbp->td_list; - tmp = head->next; - while (head != tmp) { - struct uhci_td *td = list_entry(tmp, struct uhci_td, list); - - tmp = tmp->next; + struct uhci_td *td; + list_for_each_entry(td, &urbp->td_list, list) { if (toggle) td->token |= cpu_to_le32(TD_TOKEN_TOGGLE); else td->token &= ~cpu_to_le32(TD_TOKEN_TOGGLE); - toggle ^= 1; } @@ -473,30 +435,19 @@ static int uhci_fixup_toggle(struct urb *urb, unsigned int toggle) static void uhci_append_queued_urb(struct uhci_hcd *uhci, struct urb *eurb, struct urb *urb) { struct urb_priv *eurbp, *urbp, *furbp, *lurbp; - struct list_head *tmp; struct uhci_td *lltd; eurbp = eurb->hcpriv; urbp = urb->hcpriv; /* Find the first URB in the queue */ + furbp = eurbp; if (eurbp->queued) { - struct list_head *head = &eurbp->queue_list; - - tmp = head->next; - while (tmp != head) { - struct urb_priv *turbp = - list_entry(tmp, struct urb_priv, queue_list); - - if (!turbp->queued) + list_for_each_entry(furbp, &eurbp->queue_list, queue_list) + if (!furbp->queued) break; + } - tmp = tmp->next; - } - } else - tmp = &eurbp->queue_list; - - furbp = list_entry(tmp, struct urb_priv, queue_list); lurbp = list_entry(furbp->queue_list.prev, struct urb_priv, queue_list); lltd = list_entry(lurbp->td_list.prev, struct uhci_td, list); @@ -522,9 +473,7 @@ static void uhci_append_queued_urb(struct uhci_hcd *uhci, struct urb *eurb, stru static void uhci_delete_queued_urb(struct uhci_hcd *uhci, struct urb *urb) { - struct urb_priv *urbp, *nurbp; - struct list_head *head, *tmp; - struct urb_priv *purbp; + struct urb_priv *urbp, *nurbp, *purbp, *turbp; struct uhci_td *pltd; unsigned int toggle; @@ -556,14 +505,7 @@ static void uhci_delete_queued_urb(struct uhci_hcd *uhci, struct urb *urb) toggle = uhci_toggle(td_token(pltd)) ^ 1; } - head = &urbp->queue_list; - tmp = head->next; - while (head != tmp) { - struct urb_priv *turbp; - - turbp = list_entry(tmp, struct urb_priv, queue_list); - tmp = tmp->next; - + list_for_each_entry(turbp, &urbp->queue_list, queue_list) { if (!turbp->queued) break; toggle = uhci_fixup_toggle(turbp->urb, toggle); @@ -637,7 +579,7 @@ static void uhci_remove_td_from_urb(struct uhci_td *td) static void uhci_destroy_urb_priv(struct uhci_hcd *uhci, struct urb *urb) { - struct list_head *head, *tmp; + struct uhci_td *td, *tmp; struct urb_priv *urbp; unsigned int age; @@ -660,13 +602,7 @@ static void uhci_destroy_urb_priv(struct uhci_hcd *uhci, struct urb *urb) if (list_empty(&uhci->td_remove_list)) uhci_set_next_interrupt(uhci); - head = &urbp->td_list; - tmp = head->next; - while (tmp != head) { - struct uhci_td *td = list_entry(tmp, struct uhci_td, list); - - tmp = tmp->next; - + list_for_each_entry_safe(td, tmp, &urbp->td_list, list) { uhci_remove_td_from_urb(td); uhci_remove_td(uhci, td); list_add(&td->remove_list, &uhci->td_remove_list); @@ -1083,7 +1019,6 @@ static int uhci_submit_common(struct uhci_hcd *uhci, struct urb *urb, struct urb */ static int uhci_result_common(struct uhci_hcd *uhci, struct urb *urb) { - struct list_head *tmp, *head; struct urb_priv *urbp = urb->hcpriv; struct uhci_td *td; unsigned int status = 0; @@ -1091,13 +1026,7 @@ static int uhci_result_common(struct uhci_hcd *uhci, struct urb *urb) urb->actual_length = 0; - head = &urbp->td_list; - tmp = head->next; - while (tmp != head) { - td = list_entry(tmp, struct uhci_td, list); - - tmp = tmp->next; - + list_for_each_entry(td, &urbp->td_list, list) { status = uhci_status_bits(td_status(td)); if (status & TD_CTRL_ACTIVE) return -EINPROGRESS; @@ -1176,17 +1105,12 @@ static inline int uhci_submit_interrupt(struct uhci_hcd *uhci, struct urb *urb, static int isochronous_find_limits(struct uhci_hcd *uhci, struct urb *urb, unsigned int *start, unsigned int *end) { struct urb *last_urb = NULL; - struct list_head *tmp, *head; + struct urb_priv *up; int ret = 0; - head = &uhci->urb_list; - tmp = head->next; - while (tmp != head) { - struct urb_priv *up = list_entry(tmp, struct urb_priv, urb_list); + list_for_each_entry(up, &uhci->urb_list, urb_list) { struct urb *u = up->urb; - tmp = tmp->next; - /* look for pending URB's with identical pipe handle */ if ((urb->pipe == u->pipe) && (urb->dev == u->dev) && (u->status == -EINPROGRESS) && (u != urb)) { @@ -1272,7 +1196,7 @@ static int uhci_submit_isochronous(struct uhci_hcd *uhci, struct urb *urb) static int uhci_result_isochronous(struct uhci_hcd *uhci, struct urb *urb) { - struct list_head *tmp, *head; + struct uhci_td *td; struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; int status; int i, ret = 0; @@ -1280,14 +1204,9 @@ static int uhci_result_isochronous(struct uhci_hcd *uhci, struct urb *urb) urb->actual_length = 0; i = 0; - head = &urbp->td_list; - tmp = head->next; - while (tmp != head) { - struct uhci_td *td = list_entry(tmp, struct uhci_td, list); + list_for_each_entry(td, &urbp->td_list, list) { int actlength; - tmp = tmp->next; - if (td_status(td) & TD_CTRL_ACTIVE) return -EINPROGRESS; @@ -1311,20 +1230,15 @@ static int uhci_result_isochronous(struct uhci_hcd *uhci, struct urb *urb) static struct urb *uhci_find_urb_ep(struct uhci_hcd *uhci, struct urb *urb) { - struct list_head *tmp, *head; + struct urb_priv *up; /* We don't match Isoc transfers since they are special */ if (usb_pipeisoc(urb->pipe)) return NULL; - head = &uhci->urb_list; - tmp = head->next; - while (tmp != head) { - struct urb_priv *up = list_entry(tmp, struct urb_priv, urb_list); + list_for_each_entry(up, &uhci->urb_list, urb_list) { struct urb *u = up->urb; - tmp = tmp->next; - if (u->dev == urb->dev && u->status == -EINPROGRESS) { /* For control, ignore the direction */ if (usb_pipecontrol(urb->pipe) && @@ -1475,7 +1389,8 @@ out: static void uhci_unlink_generic(struct uhci_hcd *uhci, struct urb *urb) { - struct list_head *head, *tmp; + struct list_head *head; + struct uhci_td *td; struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; int prevactive = 1; @@ -1495,15 +1410,10 @@ static void uhci_unlink_generic(struct uhci_hcd *uhci, struct urb *urb) * for all types */ head = &urbp->td_list; - tmp = head->next; - while (tmp != head) { - struct uhci_td *td = list_entry(tmp, struct uhci_td, list); - - tmp = tmp->next; - + list_for_each_entry(td, head, list) { if (!(td_status(td) & TD_CTRL_ACTIVE) && (uhci_actual_length(td_status(td)) < uhci_expected_length(td_token(td)) || - tmp == head)) + td->list.next == head)) usb_settoggle(urb->dev, uhci_endpoint(td_token(td)), uhci_packetout(td_token(td)), uhci_toggle(td_token(td)) ^ 1); @@ -1556,7 +1466,8 @@ done: static int uhci_fsbr_timeout(struct uhci_hcd *uhci, struct urb *urb) { struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; - struct list_head *head, *tmp; + struct list_head *head; + struct uhci_td *td; int count = 0; uhci_dec_fsbr(uhci, urb); @@ -1570,18 +1481,14 @@ static int uhci_fsbr_timeout(struct uhci_hcd *uhci, struct urb *urb) */ head = &urbp->td_list; - tmp = head->next; - while (tmp != head) { - struct uhci_td *td = list_entry(tmp, struct uhci_td, list); - - tmp = tmp->next; - + list_for_each_entry(td, head, list) { /* * Make sure we don't do the last one (since it'll have the * TERM bit set) as well as we skip every so many TD's to * make sure it doesn't hog the bandwidth */ - if (tmp != head && (count % DEPTH_INTERVAL) == (DEPTH_INTERVAL - 1)) + if (td->list.next != head && (count % DEPTH_INTERVAL) == + (DEPTH_INTERVAL - 1)) td->link |= UHCI_PTR_DEPTH; count++; @@ -1606,12 +1513,10 @@ static void stall_callback(unsigned long ptr) { struct usb_hcd *hcd = (struct usb_hcd *)ptr; struct uhci_hcd *uhci = hcd_to_uhci(hcd); - struct list_head list, *tmp, *head; + struct urb_priv *up; unsigned long flags; int called_uhci_finish_completion = 0; - INIT_LIST_HEAD(&list); - spin_lock_irqsave(&uhci->schedule_lock, flags); if (!list_empty(&uhci->urb_remove_list) && uhci_get_current_frame_number(uhci) != uhci->urb_remove_age) { @@ -1620,14 +1525,9 @@ static void stall_callback(unsigned long ptr) called_uhci_finish_completion = 1; } - head = &uhci->urb_list; - tmp = head->next; - while (tmp != head) { - struct urb_priv *up = list_entry(tmp, struct urb_priv, urb_list); + list_for_each_entry(up, &uhci->urb_list, urb_list) { struct urb *u = up->urb; - tmp = tmp->next; - spin_lock(&u->lock); /* Check if the FSBR timed out */ @@ -1642,17 +1542,6 @@ static void stall_callback(unsigned long ptr) if (called_uhci_finish_completion) wake_up_all(&uhci->waitqh); - head = &list; - tmp = head->next; - while (tmp != head) { - struct urb_priv *up = list_entry(tmp, struct urb_priv, urb_list); - struct urb *u = up->urb; - - tmp = tmp->next; - - uhci_urb_dequeue(hcd, u); - } - /* Really disable FSBR */ if (!uhci->fsbr && uhci->fsbrtimeout && time_after_eq(jiffies, uhci->fsbrtimeout)) { uhci->fsbrtimeout = 0; @@ -1682,15 +1571,9 @@ static int init_stall_timer(struct usb_hcd *hcd) static void uhci_free_pending_qhs(struct uhci_hcd *uhci) { - struct list_head *tmp, *head; - - head = &uhci->qh_remove_list; - tmp = head->next; - while (tmp != head) { - struct uhci_qh *qh = list_entry(tmp, struct uhci_qh, remove_list); - - tmp = tmp->next; + struct uhci_qh *qh, *tmp; + list_for_each_entry_safe(qh, tmp, &uhci->qh_remove_list, remove_list) { list_del_init(&qh->remove_list); uhci_free_qh(uhci, qh); @@ -1699,15 +1582,9 @@ static void uhci_free_pending_qhs(struct uhci_hcd *uhci) static void uhci_free_pending_tds(struct uhci_hcd *uhci) { - struct list_head *tmp, *head; - - head = &uhci->td_remove_list; - tmp = head->next; - while (tmp != head) { - struct uhci_td *td = list_entry(tmp, struct uhci_td, remove_list); - - tmp = tmp->next; + struct uhci_td *td, *tmp; + list_for_each_entry_safe(td, tmp, &uhci->td_remove_list, remove_list) { list_del_init(&td->remove_list); uhci_free_td(uhci, td); @@ -1728,19 +1605,13 @@ static void uhci_finish_urb(struct usb_hcd *hcd, struct urb *urb, struct pt_regs static void uhci_finish_completion(struct usb_hcd *hcd, struct pt_regs *regs) { struct uhci_hcd *uhci = hcd_to_uhci(hcd); - struct list_head *tmp, *head; + struct urb_priv *urbp, *tmp; - head = &uhci->complete_list; - tmp = head->next; - while (tmp != head) { - struct urb_priv *urbp = list_entry(tmp, struct urb_priv, urb_list); + list_for_each_entry_safe(urbp, tmp, &uhci->complete_list, urb_list) { struct urb *urb = urbp->urb; list_del_init(&urbp->urb_list); uhci_finish_urb(hcd, urb, regs); - - head = &uhci->complete_list; - tmp = head->next; } } @@ -1756,7 +1627,7 @@ static irqreturn_t uhci_irq(struct usb_hcd *hcd, struct pt_regs *regs) struct uhci_hcd *uhci = hcd_to_uhci(hcd); unsigned long io_addr = uhci->io_addr; unsigned short status; - struct list_head *tmp, *head; + struct urb_priv *urbp, *tmp; unsigned int age; /* @@ -1803,15 +1674,11 @@ static irqreturn_t uhci_irq(struct usb_hcd *hcd, struct pt_regs *regs) else uhci_set_next_interrupt(uhci); - /* Walk the list of pending URB's to see which ones completed */ - head = &uhci->urb_list; - tmp = head->next; - while (tmp != head) { - struct urb_priv *urbp = list_entry(tmp, struct urb_priv, urb_list); + /* Walk the list of pending URBs to see which ones completed + * (must be _safe because uhci_transfer_result() dequeues URBs) */ + list_for_each_entry_safe(urbp, tmp, &uhci->urb_list, urb_list) { struct urb *urb = urbp->urb; - tmp = tmp->next; - /* Checks the status and does all of the magic necessary */ uhci_transfer_result(uhci, urb); } -- cgit v1.2.3 From 349740f3b9bec2ae96455baea83b8e17cc64ed75 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 21 Sep 2004 02:00:55 -0700 Subject: [PATCH] USB: Fix data toggle handling in the UHCI driver This patch fixes an error in the way the UHCI driver computes data-toggle updates when unlinking a bulk or interrupt URB that hasn't transferred any data. Thanks to Thierry Chantry for finding this bug and testing the patch. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/uhci-hcd.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c index 07076731937f..ed6c7f86c6ba 100644 --- a/drivers/usb/host/uhci-hcd.c +++ b/drivers/usb/host/uhci-hcd.c @@ -1392,7 +1392,7 @@ static void uhci_unlink_generic(struct uhci_hcd *uhci, struct urb *urb) struct list_head *head; struct uhci_td *td; struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; - int prevactive = 1; + int prevactive = 0; uhci_dec_fsbr(uhci, urb); /* Safe since it checks */ @@ -1400,20 +1400,28 @@ static void uhci_unlink_generic(struct uhci_hcd *uhci, struct urb *urb) * Now we need to find out what the last successful toggle was * so we can update the local data toggle for the next transfer * - * There's 3 way's the last successful completed TD is found: + * There are 2 ways the last successful completed TD is found: * * 1) The TD is NOT active and the actual length < expected length * 2) The TD is NOT active and it's the last TD in the chain + * + * and a third way the first uncompleted TD is found: + * * 3) The TD is active and the previous TD is NOT active * * Control and Isochronous ignore the toggle, so this is safe * for all types + * + * FIXME: The toggle fixups won't be 100% reliable until we + * change over to using a single queue for each endpoint and + * stop the queue before unlinking. */ head = &urbp->td_list; list_for_each_entry(td, head, list) { if (!(td_status(td) & TD_CTRL_ACTIVE) && - (uhci_actual_length(td_status(td)) < uhci_expected_length(td_token(td)) || - td->list.next == head)) + (uhci_actual_length(td_status(td)) < + uhci_expected_length(td_token(td)) || + td->list.next == head)) usb_settoggle(urb->dev, uhci_endpoint(td_token(td)), uhci_packetout(td_token(td)), uhci_toggle(td_token(td)) ^ 1); -- cgit v1.2.3 From 1116edbfbb6c1165851257da882f443dca63ae14 Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Tue, 28 Sep 2004 01:56:39 -0700 Subject: [PATCH] PCI: fix up usb quirk __init marks Lukas Hejtmanek wrote: > > Unable to handle kernel paging request at virtual address 0000ffff > printing eip: > c0402097 > *pde = 00000000 > Oops: 0002 [#1] > PREEMPT > Modules linked in: yenta_socket pcmcia_core i830 ehci_hcd uhci_hcd rtc > CPU: 0 > EIP: 0060:[] Tainted: P VLI > EFLAGS: 00010246 (2.6.9-rc2-mm2) > EIP is at quirk_usb_early_handoff+0x0/0x3e > eax: 0000ffff ebx: c035d954 ecx: c6866000 edx: 00020000 > esi: c6866000 edi: c035da4c ebp: ceede380 esp: c7f4bed8 > ds: 007b es: 007b ss: 0068 > Process pccardd (pid: 3386, threadinfo=c7f4a000 task=c5ea2d70) > Stack: c01d2076 c6866000 c6866000 ceede380 00000000 c01d20ba c6866000 c035d81c > c035da4c c01d01ce 00000000 c6866000 00000000 00000000 c01d0214 ceede380 > 00000000 c686642c ceede380 ceede394 c7f4a000 cfbdd0de ceede380 00000000 > Call Trace: > [] pci_do_fixups+0x49/0x4b Well quirk_usb_early_handoff() should be __devinit, not __init. There are a few other things in there which look hotpluggy, and are marked __init. The whole thing needs a review. Signed-off-by: Greg Kroah-Hartman --- drivers/pci/quirks.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 8ded4fe07f51..6579764b95d2 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -866,7 +866,7 @@ static int __init usb_handoff_early(char *str) } __setup("usb-handoff", usb_handoff_early); -static void __init quirk_usb_handoff_uhci(struct pci_dev *pdev) +static void __devinit quirk_usb_handoff_uhci(struct pci_dev *pdev) { unsigned long base = 0; int wait_time, delta; @@ -919,7 +919,7 @@ static void __init quirk_usb_handoff_uhci(struct pci_dev *pdev) } -static void __init quirk_usb_handoff_ohci(struct pci_dev *pdev) +static void __devinit quirk_usb_handoff_ohci(struct pci_dev *pdev) { void __iomem *base; int wait_time; @@ -949,7 +949,7 @@ static void __init quirk_usb_handoff_ohci(struct pci_dev *pdev) iounmap(base); } -static void __init quirk_usb_disable_ehci(struct pci_dev *pdev) +static void __devinit quirk_usb_disable_ehci(struct pci_dev *pdev) { int wait_time, delta; void __iomem *base, *op_reg_base; @@ -1039,7 +1039,7 @@ static void __init quirk_usb_disable_ehci(struct pci_dev *pdev) -static void __init quirk_usb_early_handoff(struct pci_dev *pdev) +static void __devinit quirk_usb_early_handoff(struct pci_dev *pdev) { if (!usb_early_handoff) return; -- cgit v1.2.3 From 3bc5c62a11835a7317fc18512256952cb8cb9ca9 Mon Sep 17 00:00:00 2001 From: David Brownell Date: Tue, 28 Sep 2004 21:24:23 -0700 Subject: [PATCH] USB Gadget: Ethernet/RNDIS gadget, minor updates Helps battery recharge, and gives this a new version string since it's changed a lot since St. Patrick's Day. Just minor updates: - use usb_gadget_vbus_draw() when changing configs, so that USB can be used to recharge batteries (or whatever); and report power use. bMaxPower is now 100 mA except with OTG (where it's 8mA). - rename /proc/rndis/000 as /proc/driver/rndis-000, and only have it when a new CONFIG variable is set (added by a later patch) - minor fixes to make some debug-only rndis messages compile - version is now "Equinox 2004". Signed-off-by: David Brownell Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/ether.c | 40 +++++++++++++++++++++++------------- drivers/usb/gadget/rndis.c | 51 ++++++++++++++++++++++------------------------ 2 files changed, 50 insertions(+), 41 deletions(-) diff --git a/drivers/usb/gadget/ether.c b/drivers/usb/gadget/ether.c index 0c5f37c2ba0a..e3fec4a5a7f8 100644 --- a/drivers/usb/gadget/ether.c +++ b/drivers/usb/gadget/ether.c @@ -84,7 +84,7 @@ */ #define DRIVER_DESC "Ethernet Gadget" -#define DRIVER_VERSION "St Patrick's Day 2004" +#define DRIVER_VERSION "Equinox 2004" static const char shortname [] = "ether"; static const char driver_desc [] = DRIVER_DESC; @@ -391,7 +391,7 @@ eth_config = { .bConfigurationValue = DEV_CONFIG_VALUE, .iConfiguration = STRING_CDC, .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, - .bMaxPower = 1, + .bMaxPower = 50, }; #ifdef CONFIG_USB_ETH_RNDIS @@ -405,7 +405,7 @@ rndis_config = { .bConfigurationValue = DEV_RNDIS_CONFIG_VALUE, .iConfiguration = STRING_RNDIS, .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, - .bMaxPower = 1, + .bMaxPower = 50, }; #endif @@ -1202,13 +1202,20 @@ eth_set_config (struct eth_dev *dev, unsigned number, int gfp_flags) result = -EINVAL; /* FALL THROUGH */ case 0: - return result; + break; } - if (result) - eth_reset_config (dev); - else { + if (result) { + if (number) + eth_reset_config (dev); + usb_gadget_vbus_draw(dev->gadget, + dev->gadget->is_otg ? 8 : 100); + } else { char *speed; + unsigned power; + + power = 2 * eth_config.bMaxPower; + usb_gadget_vbus_draw(dev->gadget, power); switch (gadget->speed) { case USB_SPEED_FULL: speed = "full"; break; @@ -1219,8 +1226,8 @@ eth_set_config (struct eth_dev *dev, unsigned number, int gfp_flags) } dev->config = number; - INFO (dev, "%s speed config #%d: %s, using %s\n", - speed, number, driver_desc, + INFO (dev, "%s speed config #%d: %d mA, %s, using %s\n", + speed, number, power, driver_desc, dev->rndis ? "RNDIS" : (dev->cdc @@ -1379,8 +1386,9 @@ static void eth_setup_complete (struct usb_ep *ep, struct usb_request *req) static void rndis_response_complete (struct usb_ep *ep, struct usb_request *req) { if (req->status || req->actual != req->length) - DEBUG (dev, "rndis response complete --> %d, %d/%d\n", - req->status, req->actual, req->length); + DEBUG ((struct eth_dev *) ep->driver_data, + "rndis response complete --> %d, %d/%d\n", + req->status, req->actual, req->length); /* done sending after CDC_GET_ENCAPSULATED_RESPONSE */ } @@ -2102,11 +2110,13 @@ static void rndis_send_media_state (struct eth_dev *dev, int connect) } } -static void rndis_control_ack_complete (struct usb_ep *ep, struct usb_request *req) +static void +rndis_control_ack_complete (struct usb_ep *ep, struct usb_request *req) { if (req->status || req->actual != req->length) - DEBUG (dev, "rndis control ack complete --> %d, %d/%d\n", - req->status, req->actual, req->length); + DEBUG ((struct eth_dev *) ep->driver_data, + "rndis control ack complete --> %d, %d/%d\n", + req->status, req->actual, req->length); usb_ep_free_buffer(ep, req->buf, req->dma, 8); usb_ep_free_request(ep, req); @@ -2472,8 +2482,10 @@ autoconf_fail: if (gadget->is_otg) { otg_descriptor.bmAttributes |= USB_OTG_HNP, eth_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP; + eth_config.bMaxPower = 4; #ifdef CONFIG_USB_ETH_RNDIS rndis_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP; + rndis_config.bMaxPower = 4; #endif } diff --git a/drivers/usb/gadget/rndis.c b/drivers/usb/gadget/rndis.c index 1cd445a01d13..561ca545e4e5 100644 --- a/drivers/usb/gadget/rndis.c +++ b/drivers/usb/gadget/rndis.c @@ -70,8 +70,6 @@ MODULE_PARM_DESC (rndis_debug, "enable debugging"); #define RNDIS_MAX_CONFIGS 1 -static struct proc_dir_entry *rndis_connect_dir; -static struct proc_dir_entry *rndis_connect_state [RNDIS_MAX_CONFIGS]; static rndis_params rndis_per_dev_params [RNDIS_MAX_CONFIGS]; @@ -1275,6 +1273,8 @@ int rndis_rm_hdr (u8 *buf, u32 *length) return 0; } +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + int rndis_proc_read (char *page, char **start, off_t off, int count, int *eof, void *data) { @@ -1365,43 +1365,40 @@ int rndis_proc_write (struct file *file, const char __user *buffer, return count; } +#define NAME_TEMPLATE "driver/rndis-%03d" + +static struct proc_dir_entry *rndis_connect_state [RNDIS_MAX_CONFIGS]; + +#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ + + int __init rndis_init (void) { u8 i; - char name [4]; - /* FIXME this should probably be /proc/driver/rndis, - * and only if debugging is enabled - */ - - if (!(rndis_connect_dir = proc_mkdir ("rndis", NULL))) { - printk (KERN_ERR "%s: couldn't create /proc/rndis entry", - __FUNCTION__); - return -EIO; - } - for (i = 0; i < RNDIS_MAX_CONFIGS; i++) { - sprintf (name, "%03d", i); +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + char name [20]; + + sprintf (name, NAME_TEMPLATE, i); if (!(rndis_connect_state [i] - = create_proc_entry (name, 0660, - rndis_connect_dir))) + = create_proc_entry (name, 0660, NULL))) { DEBUG ("%s :remove entries", __FUNCTION__); - for (i--; i > 0; i--) { - sprintf (name, "%03d", i); - remove_proc_entry (name, rndis_connect_dir); + while (i) { + sprintf (name, NAME_TEMPLATE, --i); + remove_proc_entry (name, NULL); } DEBUG ("\n"); - - remove_proc_entry ("000", rndis_connect_dir); - remove_proc_entry ("rndis", NULL); return -EIO; } + rndis_connect_state [i]->nlink = 1; rndis_connect_state [i]->write_proc = rndis_proc_write; rndis_connect_state [i]->read_proc = rndis_proc_read; rndis_connect_state [i]->data = (void *) (rndis_per_dev_params + i); +#endif rndis_per_dev_params [i].confignr = i; rndis_per_dev_params [i].used = 0; rndis_per_dev_params [i].state = RNDIS_UNINITIALIZED; @@ -1415,14 +1412,14 @@ int __init rndis_init (void) void rndis_exit (void) { +#ifdef CONFIG_USB_GADGET_DEBUG_FILES u8 i; - char name [4]; + char name [20]; for (i = 0; i < RNDIS_MAX_CONFIGS; i++) { - sprintf (name, "%03d", i); - remove_proc_entry (name, rndis_connect_dir); + sprintf (name, NAME_TEMPLATE, i); + remove_proc_entry (name, NULL); } - remove_proc_entry ("rndis", NULL); - return; +#endif } -- cgit v1.2.3 From e1bf2cc8ca11a42544f9402f79c50f6c1dd1c75f Mon Sep 17 00:00:00 2001 From: David Brownell Date: Tue, 28 Sep 2004 21:24:57 -0700 Subject: [PATCH] USB: OHCI support for PXA27x here's a patch from Nico to add PXA27x OHCI support. Please merge; it applies on top of your current BK with some offsets, but I can't build that tree for ARM because of some #include lossage. OHCI driver for the Intel PXA27x processor Signed-off-by: Nicolas Pitre Signed-off-by: David Brownell Signed-off-by: Greg Kroah-Hartman --- arch/arm/mach-pxa/pxa27x.c | 44 ++++ drivers/usb/Kconfig | 2 +- drivers/usb/host/ohci-hcd.c | 5 + drivers/usb/host/ohci-pxa27x.c | 460 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 510 insertions(+), 1 deletion(-) create mode 100644 drivers/usb/host/ohci-pxa27x.c diff --git a/arch/arm/mach-pxa/pxa27x.c b/arch/arm/mach-pxa/pxa27x.c index b1573c837b6a..3144d48cae2c 100644 --- a/arch/arm/mach-pxa/pxa27x.c +++ b/arch/arm/mach-pxa/pxa27x.c @@ -16,8 +16,10 @@ #include #include #include +#include #include +#include #include "generic.h" @@ -116,3 +118,45 @@ unsigned int get_lcdclk_frequency_10khz(void) EXPORT_SYMBOL(get_clk_frequency_khz); EXPORT_SYMBOL(get_memclk_frequency_10khz); EXPORT_SYMBOL(get_lcdclk_frequency_10khz); + + +/* + * device registration specific to PXA27x. + */ + +static u64 pxa27x_dmamask = 0xffffffffUL; + +static struct resource pxa27x_ohci_resources[] = { + [0] = { + .start = 0x4C000000, + .end = 0x4C00ff6f, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = IRQ_USBH1, + .end = IRQ_USBH1, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device ohci_device = { + .name = "pxa27x-ohci", + .id = -1, + .dev = { + .dma_mask = &pxa27x_dmamask, + .coherent_dma_mask = 0xffffffff, + }, + .num_resources = ARRAY_SIZE(pxa27x_ohci_resources), + .resource = pxa27x_ohci_resources, +}; + +static struct platform_device *devices[] __initdata = { + &ohci_device, +}; + +static int __init pxa27x_init(void) +{ + return platform_add_devices(devices, ARRAY_SIZE(devices)); +} + +subsys_initcall(pxa27x_init); diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 0e805e4f3aa1..4354a3eb93d4 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -7,7 +7,7 @@ menu "USB support" # ARM SA1111 chips have a non-PCI based "OHCI-compatible" USB host interface. config USB tristate "Support for Host-side USB" - depends on PCI || SA1111 || ARCH_OMAP1510 || ARCH_OMAP1610 || ARCH_LH7A404 + depends on PCI || SA1111 || ARCH_OMAP1510 || ARCH_OMAP1610 || ARCH_LH7A404 || PXA27x ---help--- Universal Serial Bus (USB) is a specification for a serial bus subsystem which offers higher speeds and more features than the diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index 84867d1d0bb7..28f3fdd0751f 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -879,10 +879,15 @@ MODULE_LICENSE ("GPL"); #include "ohci-lh7a404.c" #endif +#ifdef CONFIG_PXA27x +#include "ohci-pxa27x.c" +#endif + #if !(defined(CONFIG_PCI) \ || defined(CONFIG_SA1111) \ || defined(CONFIG_ARCH_OMAP) \ || defined (CONFIG_ARCH_LH7A404) \ + || defined (CONFIG_PXA27x) \ ) #error "missing bus glue for ohci-hcd" #endif diff --git a/drivers/usb/host/ohci-pxa27x.c b/drivers/usb/host/ohci-pxa27x.c new file mode 100644 index 000000000000..4bfe74123373 --- /dev/null +++ b/drivers/usb/host/ohci-pxa27x.c @@ -0,0 +1,460 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber + * (C) Copyright 2000-2002 David Brownell + * (C) Copyright 2002 Hewlett-Packard Company + * + * Bus Glue for pxa27x + * + * Written by Christopher Hoover + * Based on fragments of previous driver by Russell King et al. + * + * Modified for LH7A404 from ohci-sa1111.c + * by Durgesh Pattamatta + * + * Modified for pxa27x from ohci-lh7a404.c + * by Nick Bane 26-8-2004 + * + * This file is licenced under the GPL. + */ + +#include +#include +#include + + +#define PMM_NPS_MODE 1 +#define PMM_GLOBAL_MODE 2 +#define PMM_PERPORT_MODE 3 + +#define PXA_UHC_MAX_PORTNUM 3 + +#define UHCRHPS(x) __REG2( 0x4C000050, (x)<<2 ) + +static int pxa27x_ohci_pmm_state; + +/* + PMM_NPS_MODE -- PMM Non-power switching mode + Ports are powered continuously. + + PMM_GLOBAL_MODE -- PMM global switching mode + All ports are powered at the same time. + + PMM_PERPORT_MODE -- PMM per port switching mode + Ports are powered individually. + */ +static int pxa27x_ohci_select_pmm( int mode ) +{ + pxa27x_ohci_pmm_state = mode; + + switch ( mode ) { + case PMM_NPS_MODE: + UHCRHDA |= RH_A_NPS; + break; + case PMM_GLOBAL_MODE: + UHCRHDA &= ~(RH_A_NPS & RH_A_PSM); + break; + case PMM_PERPORT_MODE: + UHCRHDA &= ~(RH_A_NPS); + UHCRHDA |= RH_A_PSM; + + /* Set port power control mask bits, only 3 ports. */ + UHCRHDB |= (0x7<<17); + break; + default: + printk( KERN_ERR + "Invalid mode %d, set to non-power switch mode.\n", + mode ); + + pxa27x_ohci_pmm_state = PMM_NPS_MODE; + UHCRHDA |= RH_A_NPS; + } + + return 0; +} + +/* + If you select PMM_PERPORT_MODE, you should set the port power + */ +static int pxa27x_ohci_set_port_power( int port ) +{ + if ( (pxa27x_ohci_pmm_state==PMM_PERPORT_MODE) + && (port>0) && (port0) && (portresource[0].start, + dev->resource[0].end + - dev->resource[0].start + 1, hcd_name)) { + pr_debug("request_mem_region failed"); + return -EBUSY; + } + + pxa27x_start_hc(dev); + + /* Select Power Management Mode */ + pxa27x_ohci_select_pmm( PMM_PERPORT_MODE ); + + /* If choosing PMM_PERPORT_MODE, we should set the port power before we use it. */ + if (pxa27x_ohci_set_port_power(1) < 0) + printk(KERN_ERR "Setting port 1 power failed.\n"); + + if (pxa27x_ohci_clear_port_power(2) < 0) + printk(KERN_ERR "Setting port 2 power failed.\n"); + + if (pxa27x_ohci_clear_port_power(3) < 0) + printk(KERN_ERR "Setting port 3 power failed.\n"); + + addr = ioremap(dev->resource[0].start, + dev->resource[0].end - dev->resource[0].start + 1); + if (!addr) { + pr_debug("ioremap failed"); + retval = -ENOMEM; + goto err1; + } + + hcd = driver->hcd_alloc (); + if (hcd == NULL){ + pr_debug ("hcd_alloc failed"); + retval = -ENOMEM; + goto err1; + } + + if(dev->resource[1].flags != IORESOURCE_IRQ){ + pr_debug ("resource[1] is not IORESOURCE_IRQ"); + retval = -ENOMEM; + goto err1; + } + + hcd->driver = (struct hc_driver *) driver; + hcd->description = driver->description; + hcd->irq = dev->resource[1].start; + hcd->regs = addr; + hcd->self.controller = &dev->dev; + + retval = hcd_buffer_create (hcd); + if (retval != 0) { + pr_debug ("pool alloc fail"); + goto err1; + } + + retval = request_irq (hcd->irq, usb_hcd_irq, SA_INTERRUPT, + hcd->description, hcd); + if (retval != 0) { + pr_debug("request_irq(%d) failed with retval %d\n",hcd->irq,retval); + retval = -EBUSY; + goto err2; + } + + pr_debug ("%s (pxa27x) at 0x%p, irq %d", + hcd->description, hcd->regs, hcd->irq); + + usb_bus_init (&hcd->self); + hcd->self.op = &usb_hcd_operations; + hcd->self.hcpriv = (void *) hcd; + hcd->self.bus_name = "pxa27x"; + hcd->product_desc = "PXA27x OHCI"; + + INIT_LIST_HEAD (&hcd->dev_list); + + usb_register_bus (&hcd->self); + + if ((retval = driver->start (hcd)) < 0) { + usb_hcd_pxa27x_remove(hcd, dev); + return retval; + } + + *hcd_out = hcd; + return 0; + + err2: + hcd_buffer_destroy (hcd); + if (hcd) + driver->hcd_free(hcd); + err1: + pxa27x_stop_hc(dev); + release_mem_region(dev->resource[0].start, + dev->resource[0].end + - dev->resource[0].start + 1); + return retval; +} + + +/* may be called without controller electrically present */ +/* may be called with controller, bus, and devices active */ + +/** + * usb_hcd_pxa27x_remove - shutdown processing for pxa27x-based HCDs + * @dev: USB Host Controller being removed + * Context: !in_interrupt() + * + * Reverses the effect of usb_hcd_pxa27x_probe(), first invoking + * the HCD's stop() method. It is always called from a thread + * context, normally "rmmod", "apmd", or something similar. + * + */ +void usb_hcd_pxa27x_remove (struct usb_hcd *hcd, struct platform_device *dev) +{ + void *base; + + pr_debug ("remove: %s, state %x", hcd->self.bus_name, hcd->state); + + if (in_interrupt ()) + BUG (); + + hcd->state = USB_STATE_QUIESCING; + + pr_debug ("%s: roothub graceful disconnect", hcd->self.bus_name); + usb_disconnect (&hcd->self.root_hub); + + hcd->driver->stop (hcd); + hcd->state = USB_STATE_HALT; + + free_irq (hcd->irq, hcd); + hcd_buffer_destroy (hcd); + + usb_deregister_bus (&hcd->self); + + base = hcd->regs; + hcd->driver->hcd_free (hcd); + + pxa27x_stop_hc(dev); + release_mem_region(dev->resource[0].start, + dev->resource[0].end - dev->resource[0].start + 1); +} + +/*-------------------------------------------------------------------------*/ + +static int __devinit +ohci_pxa27x_start (struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + int ret; + + ohci_dbg (ohci, "ohci_pxa27x_start, ohci:%p", ohci); + + if ((ret = ohci_init(ohci)) < 0) + return ret; + + if ((ret = ohci_run (ohci)) < 0) { + err ("can't start %s", ohci->hcd.self.bus_name); + ohci_stop (hcd); + return ret; + } + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static const struct hc_driver ohci_pxa27x_hc_driver = { + .description = hcd_name, + + /* + * generic hardware linkage + */ + .irq = ohci_irq, + .flags = HCD_USB11, + + /* + * basic lifecycle operations + */ + .start = ohci_pxa27x_start, + .stop = ohci_stop, + + /* + * memory lifecycle (except per-request) + */ + .hcd_alloc = ohci_hcd_alloc, + .hcd_free = ohci_hcd_free, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ohci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, +#ifdef CONFIG_USB_SUSPEND + .hub_suspend = ohci_hub_suspend, + .hub_resume = ohci_hub_resume, +#endif +}; + +/*-------------------------------------------------------------------------*/ + +static int ohci_hcd_pxa27x_drv_probe(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct usb_hcd *hcd = NULL; + int ret; + + pr_debug ("In ohci_hcd_pxa27x_drv_probe"); + + if (usb_disabled()) + return -ENODEV; + + ret = usb_hcd_pxa27x_probe(&ohci_pxa27x_hc_driver, &hcd, pdev); + + if (ret == 0) + dev_set_drvdata(dev, hcd); + + return ret; +} + +static int ohci_hcd_pxa27x_drv_remove(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct usb_hcd *hcd = dev_get_drvdata(dev); + + usb_hcd_pxa27x_remove(hcd, pdev); + dev_set_drvdata(dev, NULL); + return 0; +} + +static int ohci_hcd_pxa27x_drv_suspend(struct device *dev, u32 state, u32 level) +{ +// struct platform_device *pdev = to_platform_device(dev); +// struct usb_hcd *hcd = dev_get_drvdata(dev); + printk("%s: not implemented yet\n", __FUNCTION__); + + return 0; +} + +static int ohci_hcd_pxa27x_drv_resume(struct device *dev, u32 state) +{ +// struct platform_device *pdev = to_platform_device(dev); +// struct usb_hcd *hcd = dev_get_drvdata(dev); + printk("%s: not implemented yet\n", __FUNCTION__); + + return 0; +} + + +static struct device_driver ohci_hcd_pxa27x_driver = { + .name = "pxa27x-ohci", + .bus = &platform_bus_type, + .probe = ohci_hcd_pxa27x_drv_probe, + .remove = ohci_hcd_pxa27x_drv_remove, + .suspend = ohci_hcd_pxa27x_drv_suspend, + .resume = ohci_hcd_pxa27x_drv_resume, +}; + +static int __init ohci_hcd_pxa27x_init (void) +{ + pr_debug (DRIVER_INFO " (pxa27x)"); + pr_debug ("block sizes: ed %d td %d\n", + sizeof (struct ed), sizeof (struct td)); + + return driver_register(&ohci_hcd_pxa27x_driver); +} + +static void __exit ohci_hcd_pxa27x_cleanup (void) +{ + driver_unregister(&ohci_hcd_pxa27x_driver); +} + +module_init (ohci_hcd_pxa27x_init); +module_exit (ohci_hcd_pxa27x_cleanup); -- cgit v1.2.3 From 6584ec1af8160583c607fb84e85ab3645c167931 Mon Sep 17 00:00:00 2001 From: David Brownell Date: Tue, 28 Sep 2004 21:25:54 -0700 Subject: [PATCH] USB Gadget: debug files now Kconfigured This adds a new Kconfig option, so now all the usb peripheral controllers have the same way to enable debug files (or more typically, disable them). Several of the USB peripheral controller drivers have a /proc/driver/udc file that's handy when debugging, but probably not wanted otherwise. This patch adds CONFIG_USB_GADGET_DEBUG_FILES, and uses it to replace some driver-internal defines. Signed-off-by: David Brownell Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/Kconfig | 15 +++++++++++---- drivers/usb/gadget/goku_udc.c | 14 ++++---------- drivers/usb/gadget/lh7a40x_udc.c | 7 +++---- drivers/usb/gadget/net2280.c | 10 +++++++--- drivers/usb/gadget/omap_udc.c | 2 +- drivers/usb/gadget/pxa2xx_udc.c | 17 ++++------------- 6 files changed, 30 insertions(+), 35 deletions(-) diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index decbca335389..c6e0693ed1d6 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -39,6 +39,17 @@ config USB_GADGET If in doubt, say "N" and don't enable these drivers; most people don't have this kind of hardware (except maybe inside Linux PDAs). +config USB_GADGET_DEBUG_FILES + boolean "Debugging information files" + depends on USB_GADGET && PROC_FS + help + Some of the drivers in the "gadget" framework can expose + debugging information in files such as /proc/driver/udc + (for a peripheral controller). The information in these + files may help when you're troubleshooting or bringing up a + driver on a new board. Enable these files by choosing "Y" + here. If in doubt, or to conserve kernel memory, say "N". + # # USB Peripheral Controller Support # @@ -206,10 +217,6 @@ config USB_OTG Select this only if your OMAP board has a Mini-AB connector. -config USB_OMAP_PROC - boolean "/proc/driver/udc file" - depends on USB_GADGET_OMAP - endchoice config USB_GADGET_DUALSPEED diff --git a/drivers/usb/gadget/goku_udc.c b/drivers/usb/gadget/goku_udc.c index 61ce5b2eea41..f9cdb23da15b 100644 --- a/drivers/usb/gadget/goku_udc.c +++ b/drivers/usb/gadget/goku_udc.c @@ -1092,13 +1092,7 @@ static inline char *dmastr(void) return "(dma IN)"; } -/* if we're trying to save space, don't bother with this proc file */ - -#if defined(CONFIG_PROC_FS) && !defined(CONFIG_EMBEDDED) -# define UDC_PROC_FILE -#endif - -#ifdef UDC_PROC_FILE +#ifdef CONFIG_USB_GADGET_DEBUG_FILES static const char proc_node_name [] = "driver/udc"; @@ -1312,7 +1306,7 @@ done: return count - size; } -#endif /* UDC_PROC_FILE */ +#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ /*-------------------------------------------------------------------------*/ @@ -1815,7 +1809,7 @@ static void goku_remove(struct pci_dev *pdev) usb_gadget_unregister_driver(dev->driver); } -#ifdef UDC_PROC_FILE +#ifdef CONFIG_USB_GADGET_DEBUG_FILES remove_proc_entry(proc_node_name, NULL); #endif if (dev->regs) @@ -1933,7 +1927,7 @@ static int goku_probe(struct pci_dev *pdev, const struct pci_device_id *id) pci_set_master(pdev); -#ifdef UDC_PROC_FILE +#ifdef CONFIG_USB_GADGET_DEBUG_FILES create_proc_read_entry(proc_node_name, 0, NULL, udc_proc_read, dev); #endif diff --git a/drivers/usb/gadget/lh7a40x_udc.c b/drivers/usb/gadget/lh7a40x_udc.c index 772627e97a00..0def9f70e889 100644 --- a/drivers/usb/gadget/lh7a40x_udc.c +++ b/drivers/usb/gadget/lh7a40x_udc.c @@ -54,7 +54,6 @@ static const char ep0name[] = "ep0-control"; /* Local definintions. */ -#define UDC_PROC_FILE #ifndef NO_STATES static char *state_names[] = { @@ -192,7 +191,7 @@ static __inline__ void usb_clear(u32 val, u32 port) */ #define is_usb_connected() get_portc_pdr(2) -#ifdef UDC_PROC_FILE +#ifdef CONFIG_USB_GADGET_DEBUG_FILES static const char proc_node_name[] = "driver/udc"; @@ -248,12 +247,12 @@ udc_proc_read(char *page, char **start, off_t off, int count, #define create_proc_files() create_proc_read_entry(proc_node_name, 0, NULL, udc_proc_read, dev) #define remove_proc_files() remove_proc_entry(proc_node_name, NULL) -#else /* !UDC_PROC_FILE */ +#else /* !CONFIG_USB_GADGET_DEBUG_FILES */ #define create_proc_files() do {} while (0) #define remove_proc_files() do {} while (0) -#endif /* UDC_PROC_FILE */ +#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ /* * udc_disable - disable USB device controller diff --git a/drivers/usb/gadget/net2280.c b/drivers/usb/gadget/net2280.c index 9148f7ed4701..f797b67b49f8 100644 --- a/drivers/usb/gadget/net2280.c +++ b/drivers/usb/gadget/net2280.c @@ -76,7 +76,6 @@ #define EP_DONTUSE 13 /* nonzero */ #define USE_RDK_LEDS /* GPIO pins control three LEDs */ -#define USE_SYSFS_DEBUG_FILES static const char driver_name [] = "net2280"; @@ -117,7 +116,7 @@ module_param (fifo_mode, ushort, 0644); #define DIR_STRING(bAddress) (((bAddress) & USB_DIR_IN) ? "in" : "out") -#if defined(USE_SYSFS_DEBUG_FILES) || defined (DEBUG) +#if defined(CONFIG_USB_GADGET_DEBUG_FILES) || defined (DEBUG) static char *type_string (u8 bmAttributes) { switch ((bmAttributes) & USB_ENDPOINT_XFERTYPE_MASK) { @@ -1449,7 +1448,12 @@ static const struct usb_gadget_ops net2280_ops = { /*-------------------------------------------------------------------------*/ -#ifdef USE_SYSFS_DEBUG_FILES +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +/* FIXME move these into procfs, and use seq_file. + * Sysfs _still_ doesn't behave for arbitrarily sized files, + * and also doesn't help products using this with 2.4 kernels. + */ /* "function" sysfs attribute */ static ssize_t diff --git a/drivers/usb/gadget/omap_udc.c b/drivers/usb/gadget/omap_udc.c index 84de1faeab64..c321c542f0df 100644 --- a/drivers/usb/gadget/omap_udc.c +++ b/drivers/usb/gadget/omap_udc.c @@ -1976,7 +1976,7 @@ EXPORT_SYMBOL(usb_gadget_unregister_driver); /*-------------------------------------------------------------------------*/ -#ifdef CONFIG_USB_OMAP_PROC +#ifdef CONFIG_USB_GADGET_DEBUG_FILES #include diff --git a/drivers/usb/gadget/pxa2xx_udc.c b/drivers/usb/gadget/pxa2xx_udc.c index 54e32c9d2cf2..68020861b8fa 100644 --- a/drivers/usb/gadget/pxa2xx_udc.c +++ b/drivers/usb/gadget/pxa2xx_udc.c @@ -91,10 +91,6 @@ static const char ep0name [] = "ep0"; // #define USE_OUT_DMA // #define DISABLE_TEST_MODE -#ifdef CONFIG_PROC_FS -#define UDC_PROC_FILE -#endif - #ifdef CONFIG_ARCH_IXP4XX #undef USE_DMA @@ -108,12 +104,6 @@ static const char ep0name [] = "ep0"; #include "pxa2xx_udc.h" -#ifdef CONFIG_EMBEDDED -/* few strings, and little code to use them */ -#undef DEBUG -#undef UDC_PROC_FILE -#endif - #ifdef USE_DMA static int use_dma = 1; module_param(use_dma, bool, 0); @@ -1211,7 +1201,7 @@ static const struct usb_gadget_ops pxa2xx_udc_ops = { /*-------------------------------------------------------------------------*/ -#ifdef UDC_PROC_FILE +#ifdef CONFIG_USB_GADGET_DEBUG_FILES static const char proc_node_name [] = "driver/udc"; @@ -1367,11 +1357,12 @@ done: #define remove_proc_files() \ remove_proc_entry(proc_node_name, NULL) -#else /* !UDC_PROC_FILE */ +#else /* !CONFIG_USB_GADGET_DEBUG_FILES */ + #define create_proc_files() do {} while (0) #define remove_proc_files() do {} while (0) -#endif /* UDC_PROC_FILE */ +#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ /* "function" sysfs attribute */ static ssize_t -- cgit v1.2.3 From 2c5b9798d842088d3842f17774d428c0e49ce605 Mon Sep 17 00:00:00 2001 From: Matthew Dharm Date: Tue, 28 Sep 2004 21:42:28 -0700 Subject: [PATCH] USB storage: delayed device scanning This patch started life as as366, got some modifications, and lives now as as366b. It implements a delay in SCSI-layer device scanning for usb-storage devices at insertion time. Many devices work better with this delay. We believe we can remove several US_FL_FIX_INQUIRY unusual_devs.h entries with this patch. (That's a hint, Phil!) The delay is adjustable via a sysfs parameter which is global to the usb-storage module. The default is 5 seconds. Signed-off-by: Alan Stern Signed-off-by: Matthew Dharm Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/usb.c | 70 +++++++++++++++++++++++++++++++++++++++-------- drivers/usb/storage/usb.h | 2 ++ 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c index 9579bdf4969a..32e2f1482c57 100644 --- a/drivers/usb/storage/usb.c +++ b/drivers/usb/storage/usb.c @@ -97,6 +97,11 @@ MODULE_AUTHOR("Matthew Dharm "); MODULE_DESCRIPTION("USB Mass Storage driver for Linux"); MODULE_LICENSE("GPL"); +static unsigned int delay_use = 5; +module_param(delay_use, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(delay_use, "seconds to delay before using a new device"); + + static int storage_probe(struct usb_interface *iface, const struct usb_device_id *id); @@ -882,6 +887,42 @@ static void dissociate_dev(struct us_data *us) kfree(us); } +/* Thread to carry out delayed SCSI-device scanning */ +static int usb_stor_scan_thread(void * __us) +{ + struct us_data *us = (struct us_data *)__us; + + /* + * This thread doesn't need any user-level access, + * so get rid of all our resources. + */ + lock_kernel(); + daemonize("usb-stor"); + current->flags |= PF_NOFREEZE; + unlock_kernel(); + + printk(KERN_DEBUG + "usb-storage: device found at %d\n", us->pusb_dev->devnum); + + /* Wait for the timeout to expire or for a disconnect */ + if (delay_use > 0) { + printk(KERN_DEBUG "usb-storage: waiting for device " + "to settle before scanning\n"); + wait_event_interruptible_timeout(us->scsi_scan_wait, + test_bit(US_FLIDX_DISCONNECTING, &us->flags), + delay_use * HZ); + } + + /* If the device is still connected, perform the scanning */ + if (!test_bit(US_FLIDX_DISCONNECTING, &us->flags)) { + scsi_scan_host(us->host); + printk(KERN_DEBUG "usb-storage: device scan complete\n"); + } + + complete_and_exit(&us->scsi_scan_done, 0); +} + + /* Probe to see if we can drive a newly-connected USB device */ static int storage_probe(struct usb_interface *intf, const struct usb_device_id *id) @@ -903,6 +944,8 @@ static int storage_probe(struct usb_interface *intf, init_MUTEX_LOCKED(&(us->sema)); init_completion(&(us->notify)); init_waitqueue_head(&us->dev_reset_wait); + init_waitqueue_head(&us->scsi_scan_wait); + init_completion(&us->scsi_scan_done); /* Associate the us_data structure with the USB device */ result = associate_dev(us, intf); @@ -951,12 +994,10 @@ static int storage_probe(struct usb_interface *intf, if (result) goto BadDevice; - /* Acquire all the other resources */ + /* Acquire all the other resources and add the host */ result = usb_stor_acquire_resources(us); if (result) goto BadDevice; - - /* Finally, add the host (this does SCSI device scanning) */ result = scsi_add_host(us->host, &intf->dev); if (result) { printk(KERN_WARNING USB_STORAGE @@ -964,10 +1005,15 @@ static int storage_probe(struct usb_interface *intf, goto BadDevice; } - scsi_scan_host(us->host); + /* Start up the thread for delayed SCSI-device scanning */ + result = kernel_thread(usb_stor_scan_thread, us, CLONE_VM); + if (result < 0) { + printk(KERN_WARNING USB_STORAGE + "Unable to start the device-scanning thread\n"); + scsi_remove_host(us->host); + goto BadDevice; + } - printk(KERN_DEBUG - "USB Mass Storage device found at %d\n", us->pusb_dev->devnum); return 0; /* We come here if there are any problems */ @@ -991,6 +1037,11 @@ static void storage_disconnect(struct usb_interface *intf) usb_stor_stop_transport(us); wake_up(&us->dev_reset_wait); + /* Interrupt the SCSI-device-scanning thread's time delay, and + * wait for the thread to finish */ + wake_up(&us->scsi_scan_wait); + wait_for_completion(&us->scsi_scan_done); + /* Wait for the current command to finish, then remove the host */ down(&us->dev_semaphore); up(&us->dev_semaphore); @@ -1012,12 +1063,9 @@ static int __init usb_stor_init(void) /* register the driver, return usb_register return code if error */ retval = usb_register(&usb_storage_driver); - if (retval) - goto out; + if (retval == 0) + printk(KERN_INFO "USB Mass Storage support registered.\n"); - /* we're all set */ - printk(KERN_INFO "USB Mass Storage support registered.\n"); -out: return retval; } diff --git a/drivers/usb/storage/usb.h b/drivers/usb/storage/usb.h index 785f30be68c5..17b25a44172d 100644 --- a/drivers/usb/storage/usb.h +++ b/drivers/usb/storage/usb.h @@ -161,6 +161,8 @@ struct us_data { struct semaphore sema; /* to sleep thread on */ struct completion notify; /* thread begin/end */ wait_queue_head_t dev_reset_wait; /* wait during reset */ + wait_queue_head_t scsi_scan_wait; /* wait before scanning */ + struct completion scsi_scan_done; /* scan thread end */ /* subdriver information */ void *extra; /* Any extra data */ -- cgit v1.2.3 From f6b3ed42bf041d1799964e041cb9de44167cc12d Mon Sep 17 00:00:00 2001 From: Matthew Dharm Date: Tue, 28 Sep 2004 21:42:55 -0700 Subject: [PATCH] USB Storage: ignore bogus residue values This patch allows usb-storage to ignore the reported residue values when required by some devices. A few devices are included... I know more are waiting to be merged into unusual_devs.h In case anyone is curious... yes, these are broken devices. Signed-off-by: Alan Stern Signed-off-by: Matthew Dharm Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/transport.c | 9 +++++++-- drivers/usb/storage/unusual_devs.h | 23 ++++++++++++++++++++++- drivers/usb/storage/usb.h | 1 + 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/drivers/usb/storage/transport.c b/drivers/usb/storage/transport.c index 42f784321607..e0459825fd45 100644 --- a/drivers/usb/storage/transport.c +++ b/drivers/usb/storage/transport.c @@ -1055,8 +1055,13 @@ int usb_stor_Bulk_transport(struct scsi_cmnd *srb, struct us_data *us) /* try to compute the actual residue, based on how much data * was really transferred and what the device tells us */ - residue = min(residue, transfer_length); - srb->resid = max(srb->resid, (int) residue); + if (residue) { + if (!(us->flags & US_FL_IGNORE_RESIDUE) || + srb->sc_data_direction == DMA_TO_DEVICE) { + residue = min(residue, transfer_length); + srb->resid = max(srb->resid, (int) residue); + } + } /* based on the status code, we report good or bad */ switch (bcs->Status) { diff --git a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h index 9d0d846e19e3..1d5181430529 100644 --- a/drivers/usb/storage/unusual_devs.h +++ b/drivers/usb/storage/unusual_devs.h @@ -268,6 +268,13 @@ UNUSUAL_DEV( 0x0525, 0xa140, 0x0100, 0x0100, US_SC_8070, US_PR_BULK, NULL, US_FL_FIX_INQUIRY ), +/* Reported by Iacopo Spalletti */ +UNUSUAL_DEV( 0x052b, 0x1807, 0x0100, 0x0100, + "Tekom Technologies, Inc", + "300_CAMERA", + US_SC_DEVICE, US_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + /* This entry is needed because the device reports Sub=ff */ UNUSUAL_DEV( 0x054c, 0x0010, 0x0106, 0x0450, "Sony", @@ -811,7 +818,14 @@ UNUSUAL_DEV( 0x0ed1, 0x6660, 0x0100, 0x0300, "Solid state disk", US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_FIX_INQUIRY ), - + +/* Reported by Rastislav Stanik */ +UNUSUAL_DEV( 0x0ea0, 0x6828, 0x0110, 0x0110, + "USB", + "Flash Disk", + US_SC_DEVICE, US_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + /* Reported by Kevin Cernekee * Tested on hardware version 1.10. * Entry is needed only for the initializer function override. @@ -833,6 +847,13 @@ UNUSUAL_DEV( 0x1065, 0x2136, 0x0000, 0x9999, US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_MODE_XLATE ), +/* Reported by Kotrla Vitezslav */ +UNUSUAL_DEV( 0x1370, 0x6828, 0x0110, 0x0110, + "SWISSBIT", + "Black Silver", + US_SC_DEVICE, US_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + #ifdef CONFIG_USB_STORAGE_SDDR55 UNUSUAL_DEV( 0x55aa, 0xa103, 0x0000, 0x9999, "Sandisk", diff --git a/drivers/usb/storage/usb.h b/drivers/usb/storage/usb.h index 17b25a44172d..199594967870 100644 --- a/drivers/usb/storage/usb.h +++ b/drivers/usb/storage/usb.h @@ -73,6 +73,7 @@ struct us_unusual_dev { #define US_FL_SCM_MULT_TARG 0x00000020 /* supports multiple targets */ #define US_FL_FIX_INQUIRY 0x00000040 /* INQUIRY response needs faking */ #define US_FL_FIX_CAPACITY 0x00000080 /* READ CAPACITY response too big */ +#define US_FL_IGNORE_RESIDUE 0x00000100 /* reported residue is wrong */ /* Dynamic flag definitions: used in set_bit() etc. */ #define US_FLIDX_URB_ACTIVE 18 /* 0x00040000 current_urb is in use */ -- cgit v1.2.3 From e4b60557a2d30de110b1b6659b432aaca50ebd58 Mon Sep 17 00:00:00 2001 From: Matthew Dharm Date: Tue, 28 Sep 2004 21:43:21 -0700 Subject: [PATCH] USB Storage: revert GetMaxLUN strictness This is patch as384. It reverts some of our sanity checks on the GetMaxLUN part of the Bulk-only protocol. Apparently, this is one area where vendors can't even get close to correct. So, in the face of any sort of error, we assume a single LUN. We also include some comments so we don't make this mistake again. Signed-off-by: Alan Stern Signed-off-by: Matthew Dharm Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/transport.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/usb/storage/transport.c b/drivers/usb/storage/transport.c index e0459825fd45..07b919d800a1 100644 --- a/drivers/usb/storage/transport.c +++ b/drivers/usb/storage/transport.c @@ -911,7 +911,6 @@ int usb_stor_Bulk_max_lun(struct us_data *us) int result; /* issue the command */ - us->iobuf[0] = 0; result = usb_stor_control_msg(us, us->recv_ctrl_pipe, US_BULK_GET_MAX_LUN, USB_DIR_IN | USB_TYPE_CLASS | @@ -922,7 +921,7 @@ int usb_stor_Bulk_max_lun(struct us_data *us) result, us->iobuf[0]); /* if we have a successful request, return the result */ - if (result >= 0) + if (result > 0) return us->iobuf[0]; /* @@ -934,13 +933,16 @@ int usb_stor_Bulk_max_lun(struct us_data *us) if (result == -EPIPE) { usb_stor_clear_halt(us, us->recv_bulk_pipe); usb_stor_clear_halt(us, us->send_bulk_pipe); - /* return the default -- no LUNs */ - return 0; } - /* An answer or a STALL are the only valid responses. If we get - * something else, return an indication of error */ - return -1; + /* + * Some devices don't like GetMaxLUN. They may STALL the control + * pipe, they may return a zero-length result, they may do nothing at + * all and timeout, or they may fail in even more bizarrely creative + * ways. In these cases the best approach is to use the default + * value: only one LUN. + */ + return 0; } int usb_stor_Bulk_transport(struct scsi_cmnd *srb, struct us_data *us) -- cgit v1.2.3 From b0178e2c364f579f220a80926c7fe55877d37271 Mon Sep 17 00:00:00 2001 From: Luiz Capitulino Date: Tue, 28 Sep 2004 22:48:33 -0700 Subject: [PATCH] usb-serial: Moves the search in device list out of usb_serial_probe(). Move the search in `usb_serial_driver_list' out of usb_serial_probe(). Note that: 1) The `found' variable is not necessary; 2) If the device does have a probe function, I'm calling usb_match_id() again. I'm uncertain if this is the better thing to do. Signed-off-by: Luiz Capitulino Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/usb-serial.c | 46 ++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index 73efa4270b79..5e550ab6ec72 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -846,6 +846,25 @@ static struct usb_serial * create_serial (struct usb_device *dev, return serial; } +static struct usb_serial_device_type *search_serial_device(struct usb_interface *iface) +{ + struct list_head *p; + const struct usb_device_id *id; + struct usb_serial_device_type *t; + + /* List trough know devices and see if the usb id matches */ + list_for_each(p, &usb_serial_driver_list) { + t = list_entry(p, struct usb_serial_device_type, driver_list); + id = usb_match_id(iface, t->id_table); + if (id != NULL) { + dbg("descriptor matches"); + return t; + } + } + + return NULL; +} + int usb_serial_probe(struct usb_interface *interface, const struct usb_device_id *id) { @@ -858,9 +877,7 @@ int usb_serial_probe(struct usb_interface *interface, struct usb_endpoint_descriptor *bulk_in_endpoint[MAX_NUM_PORTS]; struct usb_endpoint_descriptor *bulk_out_endpoint[MAX_NUM_PORTS]; struct usb_serial_device_type *type = NULL; - struct list_head *tmp; int retval; - int found; int minor; int buffer_size; int i; @@ -869,22 +886,9 @@ int usb_serial_probe(struct usb_interface *interface, int num_bulk_out = 0; int num_ports = 0; int max_endpoints; - const struct usb_device_id *id_pattern = NULL; - - /* loop through our list of known serial converters, and see if this - device matches. */ - found = 0; - list_for_each (tmp, &usb_serial_driver_list) { - type = list_entry(tmp, struct usb_serial_device_type, driver_list); - id_pattern = usb_match_id(interface, type->id_table); - if (id_pattern != NULL) { - dbg("descriptor matches"); - found = 1; - break; - } - } - if (!found) { - /* no match */ + + type = search_serial_device(interface); + if (!type) { dbg("none matched"); return -ENODEV; } @@ -897,12 +901,16 @@ int usb_serial_probe(struct usb_interface *interface, /* if this device type has a probe function, call it */ if (type->probe) { + const struct usb_device_id *id; + if (!try_module_get(type->owner)) { dev_err(&interface->dev, "module get failed, exiting\n"); kfree (serial); return -EIO; } - retval = type->probe (serial, id_pattern); + + id = usb_match_id(interface, type->id_table); + retval = type->probe(serial, id); module_put(type->owner); if (retval) { -- cgit v1.2.3 From 3b8b2da5bbeaf701f97c039ba238fcaa2cd175cc Mon Sep 17 00:00:00 2001 From: Luiz Capitulino Date: Tue, 28 Sep 2004 22:48:57 -0700 Subject: [PATCH] usb-serial: create_serial() return value trivial fix. create_serial() only returns NULL if there is no memory enough to a new `usb_serial' structure, thus, the right error code to return is -ENOMEM. Signed-off-by: Luiz Capitulino Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/usb-serial.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index 5e550ab6ec72..a25d089202e8 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -896,7 +896,7 @@ int usb_serial_probe(struct usb_interface *interface, serial = create_serial (dev, interface, type); if (!serial) { dev_err(&interface->dev, "%s - out of memory\n", __FUNCTION__); - return -ENODEV; + return -ENOMEM; } /* if this device type has a probe function, call it */ -- cgit v1.2.3 From 10503542d27a021a1bacb28de0f1730c800f31fa Mon Sep 17 00:00:00 2001 From: Luiz Capitulino Date: Tue, 28 Sep 2004 22:49:23 -0700 Subject: [PATCH] usb-serial: return_serial() trivial cleanup. return_serial() trivial cleanup: 1) CodingStyle fix; 2) The `return' is not necessary, we are at the end of a function which don't return nothing (void). Signed-off-by: Luiz Capitulino Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/usb-serial.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index a25d089202e8..41f78bc5d590 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -405,7 +405,7 @@ static struct usb_serial *get_free_serial (struct usb_serial *serial, int num_po return NULL; } -static void return_serial (struct usb_serial *serial) +static void return_serial(struct usb_serial *serial) { int i; @@ -417,8 +417,6 @@ static void return_serial (struct usb_serial *serial) for (i = 0; i < serial->num_ports; ++i) { serial_table[serial->minor + i] = NULL; } - - return; } static void destroy_serial(struct kref *kref) -- cgit v1.2.3 From 2ab98bc1040248f25ed1f2b9638509856778227b Mon Sep 17 00:00:00 2001 From: Luiz Capitulino Date: Tue, 28 Sep 2004 22:49:52 -0700 Subject: [PATCH] usb-serial: usb_serial_register() cleanup. usb_serial_register() cleanup 1) CodingStyle in the call of usb_serial_bus_register() 2) The goto and the duplicate `return retval' are not necessary Signed-off-by: Luiz Capitulino Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/usb-serial.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index 41f78bc5d590..2359fb886875 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -1338,17 +1338,13 @@ int usb_serial_register(struct usb_serial_device_type *new_device) /* Add this device to our list of devices */ list_add(&new_device->driver_list, &usb_serial_driver_list); - retval = usb_serial_bus_register (new_device); - - if (retval) - goto error; - - info("USB Serial support registered for %s", new_device->name); - - return retval; -error: - err("problem %d when registering driver %s", retval, new_device->name); - list_del(&new_device->driver_list); + retval = usb_serial_bus_register(new_device); + if (retval) { + err("problem %d when registering driver %s", retval, new_device->name); + list_del(&new_device->driver_list); + } + else + info("USB Serial support registered for %s", new_device->name); return retval; } -- cgit v1.2.3 From e3797e9df58e6cd3270137b1da1e6319a17a7fc7 Mon Sep 17 00:00:00 2001 From: Luiz Capitulino Date: Tue, 28 Sep 2004 22:50:15 -0700 Subject: [PATCH] usb-serial: Add module version information. Add module version information for drivers/usb/serial/usb-serial.c. Signed-off-by: Luiz Capitulino Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/usb-serial.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index 2359fb886875..fe4af3e1ec4b 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -1371,6 +1371,7 @@ EXPORT_SYMBOL(usb_serial_port_softint); /* Module information */ MODULE_AUTHOR( DRIVER_AUTHOR ); MODULE_DESCRIPTION( DRIVER_DESC ); +MODULE_VERSION( DRIVER_VERSION ); MODULE_LICENSE("GPL"); module_param(debug, bool, S_IRUGO | S_IWUSR); -- cgit v1.2.3 From 6af0e11bc00c5eb333dab6f41fb9ba3956d4f664 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Tue, 28 Sep 2004 22:50:37 -0700 Subject: [PATCH] USB: remove calls to usb_unlink_urb in net/catc.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/net/catc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/net/catc.c b/drivers/usb/net/catc.c index de9bf863ef49..7a3efd1338fd 100644 --- a/drivers/usb/net/catc.c +++ b/drivers/usb/net/catc.c @@ -765,10 +765,10 @@ static int catc_stop(struct net_device *netdev) if (!catc->is_f5u011) del_timer_sync(&catc->timer); - usb_unlink_urb(catc->rx_urb); - usb_unlink_urb(catc->tx_urb); - usb_unlink_urb(catc->irq_urb); - usb_unlink_urb(catc->ctrl_urb); + usb_kill_urb(catc->rx_urb); + usb_kill_urb(catc->tx_urb); + usb_kill_urb(catc->irq_urb); + usb_kill_urb(catc->ctrl_urb); return 0; } -- cgit v1.2.3 From 4afa423c4b1ec1d8d73bae1dcd1b689425199bd6 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Tue, 28 Sep 2004 22:50:58 -0700 Subject: [PATCH] USB: remove calls to usb_unlink_urb in misc/legousbtower.c another trivial one. Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/legousbtower.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/misc/legousbtower.c b/drivers/usb/misc/legousbtower.c index c9a822f2cf96..2fe2a1cb8b49 100644 --- a/drivers/usb/misc/legousbtower.c +++ b/drivers/usb/misc/legousbtower.c @@ -505,12 +505,12 @@ static void tower_abort_transfers (struct lego_usb_tower *dev) dev->interrupt_in_running = 0; mb(); if (dev->interrupt_in_urb != NULL && dev->udev) { - usb_unlink_urb (dev->interrupt_in_urb); + usb_kill_urb (dev->interrupt_in_urb); } } if (dev->interrupt_out_busy) { if (dev->interrupt_out_urb != NULL && dev->udev) { - usb_unlink_urb (dev->interrupt_out_urb); + usb_kill_urb (dev->interrupt_out_urb); } } -- cgit v1.2.3 From 81ff542ab3cd7126776e03011f1304f55f1ecdec Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Tue, 28 Sep 2004 22:51:18 -0700 Subject: [PATCH] USB: remove _some_ calls to usb_unlink_urb in misc/auerswald.c here's a more controversial one. Firstly, auerchain_unlink_urb:1180 is a wrapper for usb_unlink_urb so i don't think we should change that. Secondly, auerswald_int_release returns an int which is the error code returned by usb_unlink_urb. usb_kill_urb is a void, however, so I've removed the error checking. For the sake of debugging, the dbg-line announcing the name of the function is probably enough. Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/auerswald.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/drivers/usb/misc/auerswald.c b/drivers/usb/misc/auerswald.c index d248186f3a75..28e1736e18d4 100644 --- a/drivers/usb/misc/auerswald.c +++ b/drivers/usb/misc/auerswald.c @@ -516,7 +516,7 @@ static void auerchain_unlink_all (pauerchain_t acp) urbp = acep->urbp; urbp->transfer_flags &= ~URB_ASYNC_UNLINK; dbg ("unlink active urb"); - usb_unlink_urb (urbp); + usb_kill_urb (urbp); } } @@ -1171,22 +1171,16 @@ intoend: endpoint. This function returns 0 if successful or an error code. NOTE: no mutex please! */ -static int auerswald_int_release (pauerswald_t cp) +static void auerswald_int_release (pauerswald_t cp) { - int ret = 0; dbg ("auerswald_int_release"); /* stop the int endpoint */ - if (cp->inturbp) { - ret = usb_unlink_urb (cp->inturbp); - if (ret) - dbg ("nonzero int unlink result received: %d", ret); - } + if (cp->inturbp) + usb_kill_urb (cp->inturbp); /* deallocate memory */ auerswald_int_free (cp); - - return ret; } /* --------------------------------------------------------------------- */ -- cgit v1.2.3 From 957f22f3569378cec8fc5cbae38ac676ed691264 Mon Sep 17 00:00:00 2001 From: Pete Zaitcev Date: Wed, 29 Sep 2004 00:51:35 -0700 Subject: [PATCH] USB: Fixes for ub in 2.4.9-rc2-mm2 - Do retries for a memory key which was handed out on Kernel Summit 04. - Add missing del_timer. - Add shifts for a 2KB block size device, from Pat LaVarre. Signed-off-by: Pete Zaitcev Signed-off-by: Greg Kroah-Hartman --- drivers/block/ub.c | 53 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/drivers/block/ub.c b/drivers/block/ub.c index 184b8a22e86f..efaf8264c345 100644 --- a/drivers/block/ub.c +++ b/drivers/block/ub.c @@ -25,6 +25,7 @@ * -- prune comments, they are too volumnous * -- Exterminate P3 printks * -- Resove XXX's + * -- Redo "benh's retries", perhaps have spin-up code to handle them. V:D=? */ #include #include @@ -157,7 +158,8 @@ struct ub_scsi_cmd { struct ub_scsi_cmd *next; int error; /* Return code - valid upon done */ - int act_len; /* Return size */ + unsigned int act_len; /* Return size */ + unsigned char key, asc, ascq; /* May be valid if error==-EIO */ int stat_count; /* Retries getting status. */ @@ -673,9 +675,12 @@ static inline int ub_bd_rq_fn_1(request_queue_t *q) /* * build the command + * + * The call to blk_queue_hardsect_size() guarantees that request + * is aligned, but it is given in terms of 512 byte units, always. */ - block = rq->sector; - nblks = rq->nr_sectors; + block = rq->sector >> sc->capacity.bshift; + nblks = rq->nr_sectors >> sc->capacity.bshift; memset(cmd, 0, sizeof(struct ub_scsi_cmd)); cmd->cdb[0] = (ub_dir == UB_DIR_READ)? READ_10: WRITE_10; @@ -690,7 +695,7 @@ static inline int ub_bd_rq_fn_1(request_queue_t *q) cmd->dir = ub_dir; cmd->state = UB_CMDST_INIT; cmd->data = rq->buffer; - cmd->len = nblks * 512; + cmd->len = rq->nr_sectors * 512; cmd->done = ub_rw_cmd_done; cmd->back = rq; @@ -837,6 +842,7 @@ static void ub_urb_complete(struct urb *urb, struct pt_regs *pt) { struct ub_dev *sc = urb->context; + del_timer(&sc->work_timer); ub_complete(&sc->work_done); tasklet_schedule(&sc->tasklet); } @@ -1141,16 +1147,8 @@ static void ub_scsi_urb_compl(struct ub_dev *sc, struct ub_scsi_cmd *cmd) (*cmd->done)(sc, cmd); } else if (cmd->state == UB_CMDST_SENSE) { - /* - * We do not look at sense, because even if there was no sense, - * we get into UB_CMDST_SENSE from a STALL or CSW FAIL only. - * We request sense because we want to clear CHECK CONDITION - * on devices with delusions of SCSI, and not because we - * are curious in any way about the sense itself. - */ - /* if ((cmd->top_sense[2] & 0x0F) == NO_SENSE) { foo } */ - ub_state_done(sc, cmd, -EIO); + } else { printk(KERN_WARNING "%s: " "wrong command state %d on device %u\n", @@ -1309,6 +1307,10 @@ static void ub_top_sense_done(struct ub_dev *sc, struct ub_scsi_cmd *scmd) */ ub_cmdtr_sense(sc, scmd, sense); + /* + * Find the command which triggered the unit attention or a check, + * save the sense into it, and advance its state machine. + */ if ((cmd = ub_cmdq_peek(sc)) == NULL) { printk(KERN_WARNING "%s: sense done while idle\n", sc->name); return; @@ -1326,6 +1328,10 @@ static void ub_top_sense_done(struct ub_dev *sc, struct ub_scsi_cmd *scmd) return; } + cmd->key = sense[2] & 0x0F; + cmd->asc = sense[12]; + cmd->ascq = sense[13]; + ub_scsi_urb_compl(sc, cmd); } @@ -1519,7 +1525,7 @@ static int ub_bd_revalidate(struct gendisk *disk) sc->name, sc->dev->devnum, sc->capacity.nsec, sc->capacity.bsize); /* XXX Support sector size switching like in sr.c */ - // blk_queue_hardsect_size(q, sc->capacity.bsize); + blk_queue_hardsect_size(disk->queue, sc->capacity.bsize); set_capacity(disk, sc->capacity.nsec); // set_disk_ro(sdkp->disk, sc->readonly); @@ -1621,6 +1627,9 @@ static int ub_sync_tur(struct ub_dev *sc) rc = cmd->error; + if (rc == -EIO && cmd->key != 0) /* Retries for benh's key */ + rc = cmd->key; + err_submit: kfree(cmd); err_alloc: @@ -1836,6 +1845,7 @@ static int ub_probe(struct usb_interface *intf, request_queue_t *q; struct gendisk *disk; int rc; + int i; rc = -ENOMEM; if ((sc = kmalloc(sizeof(struct ub_dev), GFP_KERNEL)) == NULL) @@ -1902,7 +1912,11 @@ static int ub_probe(struct usb_interface *intf, * has to succeed, so we clear checks with an additional one here. * In any case it's not our business how revaliadation is implemented. */ - ub_sync_tur(sc); + for (i = 0; i < 3; i++) { /* Retries for benh's key */ + if ((rc = ub_sync_tur(sc)) <= 0) break; + if (rc != 0x6) break; + msleep(10); + } sc->removable = 1; /* XXX Query this from the device */ @@ -1938,7 +1952,7 @@ static int ub_probe(struct usb_interface *intf, blk_queue_max_phys_segments(q, UB_MAX_REQ_SG); // blk_queue_segment_boundary(q, CARM_SG_BOUNDARY); blk_queue_max_sectors(q, UB_MAX_SECTORS); - // blk_queue_hardsect_size(q, xxxxx); + blk_queue_hardsect_size(q, sc->capacity.bsize); /* * This is a serious infraction, caused by a deficiency in the @@ -2046,6 +2060,13 @@ static void ub_disconnect(struct usb_interface *intf) } spin_unlock_irqrestore(&sc->lock, flags); + /* + * There is virtually no chance that other CPU runs times so long + * after ub_urb_complete should have called del_timer, but only if HCD + * didn't forget to deliver a callback on unlink. + */ + del_timer_sync(&sc->work_timer); + /* * At this point there must be no commands coming from anyone * and no URBs left in transit. -- cgit v1.2.3 From c1e15eeb5643f6ca4287f3f0d62f005d1711bd49 Mon Sep 17 00:00:00 2001 From: Duncan Sands Date: Wed, 29 Sep 2004 00:52:03 -0700 Subject: [PATCH] usb: extract sensible strings from buggy string descriptors The Freebox is a USB modem popular in France. It returns bogus string descriptors: while the string part is there, the length and type bytes are both zero. This patch detects that case and tries to recover a sensible string by scanning for printable Latin characters. This not only causes the modem to spring to life (because usbnet gets a valid MAC address) - it also means you get the correct Product and Manufacturer strings in sysfs and elsewhere. This patch is in the "mostly harmless" category. Signed-off-by: Duncan Sands Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/message.c | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index e20dde5d793a..f2cd4770eb4f 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include #include "hcd.h" /* for usbcore internals */ @@ -623,6 +625,20 @@ int usb_get_string(struct usb_device *dev, unsigned short langid, return result; } +static void usb_try_string_workarounds(unsigned char *buf, int *length) +{ + int newlength, oldlength = *length; + + for (newlength = 2; newlength + 1 < oldlength; newlength += 2) + if (!isprint(buf[newlength]) || buf[newlength + 1]) + break; + + if (newlength > 2) { + buf[0] = newlength; + *length = newlength; + } +} + static int usb_string_sub(struct usb_device *dev, unsigned int langid, unsigned int index, unsigned char *buf) { @@ -634,19 +650,26 @@ static int usb_string_sub(struct usb_device *dev, unsigned int langid, /* If that failed try to read the descriptor length, then * ask for just that many bytes */ - if (rc < 0) { + if (rc < 2) { rc = usb_get_string(dev, langid, index, buf, 2); if (rc == 2) rc = usb_get_string(dev, langid, index, buf, buf[0]); } - if (rc >= 0) { + if (rc >= 2) { + if (!buf[0] && !buf[1]) + usb_try_string_workarounds(buf, &rc); + /* There might be extra junk at the end of the descriptor */ if (buf[0] < rc) rc = buf[0]; - if (rc < 2) - rc = -EINVAL; + + rc = rc - (rc & 1); /* force a multiple of two */ } + + if (rc < 2) + rc = (rc < 0 ? rc : -EINVAL); + return rc; } @@ -724,6 +747,9 @@ int usb_string(struct usb_device *dev, int index, char *buf, size_t size) buf[idx] = 0; err = idx; + if (tbuf[1] != USB_DT_STRING) + dev_dbg(&dev->dev, "wrong descriptor type %02x for string %d (\"%s\")\n", tbuf[1], index, buf); + errout: kfree(tbuf); return err; -- cgit v1.2.3 From 8e8f500e90f77638bebe46d86220bb9cf99ca69e Mon Sep 17 00:00:00 2001 From: Alex Kanavin Date: Wed, 29 Sep 2004 00:52:26 -0700 Subject: [PATCH] USB: export inteface and configuration strings to sysfs this patch adds exporting of configuration and interface strings to sysfs. both configuration and interface strings are present on my Nokia 7610 phone, which can be connected via USB, and I thought it would be a good idea to make them easily accessible via sysfs: [root@cs181035096 usb1]# cat 1-1/manufacturer 1-1/product Nokia 7610 [root@cs181035096 usb1]# grep "" `find . -name configuration` ./1-1/configuration:First and Last and Always [root@cs181035096 usb1]# grep "" `find . -name interface` ./1-1/1-1:1.6/interface:CDC Data Interface ./1-1/1-1:1.5/interface:CDC Comms Interface ./1-1/1-1:1.3/interface:PC Suite Services ./1-1/1-1:1.1/interface:SYNCML-SYNC The first two interfaces are for accessing the GPRS modem, they are recognized and supported perfectly by the cdc_acm driver. The last two are CDC OBEX interfaces, for which there is no driver currently, but I plan to write one. This would allow userspace to do really nifty things, for example accessing the phone filesystem, and exchanging contacts and calendar entries via SyncML. Pretty much the same thing that Nokia PC Suite does. But the software needs to distinguish between the two OBEX interfaces (which is syncml and which is file transfer? the only way to know is to read the strings), and that"s why this patch was written. Signed-off-by: Alex Kanavin Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/sysfs.c | 60 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index 007baee22013..bae974d587ae 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -27,11 +27,13 @@ static ssize_t show_##field (struct device *dev, char *buf) \ { \ struct usb_device *udev; \ + struct usb_host_config *actconfig; \ \ udev = to_usb_device (dev); \ - if (udev->actconfig) \ + actconfig = udev->actconfig; \ + if (actconfig) \ return sprintf (buf, format_string, \ - udev->actconfig->desc.field * multiplier); \ + actconfig->desc.field * multiplier); \ else \ return 0; \ } \ @@ -44,6 +46,28 @@ usb_actconfig_attr (bNumInterfaces, 1, "%2d\n") usb_actconfig_attr (bmAttributes, 1, "%2x\n") usb_actconfig_attr (bMaxPower, 2, "%3dmA\n") +#define usb_actconfig_str(name, field) \ +static ssize_t show_##name(struct device *dev, char *buf) \ +{ \ + struct usb_device *udev; \ + struct usb_host_config *actconfig; \ + int len; \ + \ + udev = to_usb_device (dev); \ + actconfig = udev->actconfig; \ + if (!actconfig) \ + return 0; \ + len = usb_string(udev, actconfig->desc.field, buf, PAGE_SIZE); \ + if (len < 0) \ + return 0; \ + buf[len] = '\n'; \ + buf[len+1] = 0; \ + return len+1; \ +} \ +static DEVICE_ATTR(name, S_IRUGO, show_##name, NULL); + +usb_actconfig_str (configuration, iConfiguration) + /* configuration value is always present, and r/w */ usb_actconfig_show(bConfigurationValue, 1, "%u\n"); @@ -198,6 +222,7 @@ void usb_create_sysfs_dev_files (struct usb_device *udev) device_create_file (dev, &dev_attr_product); if (udev->descriptor.iSerialNumber) device_create_file (dev, &dev_attr_serial); + device_create_file (dev, &dev_attr_configuration); } void usb_remove_sysfs_dev_files (struct usb_device *udev) @@ -212,6 +237,7 @@ void usb_remove_sysfs_dev_files (struct usb_device *udev) device_remove_file(dev, &dev_attr_product); if (udev->descriptor.iSerialNumber) device_remove_file(dev, &dev_attr_serial); + device_remove_file (dev, &dev_attr_configuration); } /* Interface fields */ @@ -231,7 +257,26 @@ usb_intf_attr (bNumEndpoints, "%02x\n") usb_intf_attr (bInterfaceClass, "%02x\n") usb_intf_attr (bInterfaceSubClass, "%02x\n") usb_intf_attr (bInterfaceProtocol, "%02x\n") -usb_intf_attr (iInterface, "%02x\n") + +#define usb_intf_str(name, field) \ +static ssize_t show_##name(struct device *dev, char *buf) \ +{ \ + struct usb_interface *intf; \ + struct usb_device *udev; \ + int len; \ + \ + intf = to_usb_interface (dev); \ + udev = interface_to_usbdev (intf); \ + len = usb_string(udev, intf->cur_altsetting->desc.field, buf, PAGE_SIZE);\ + if (len < 0) \ + return 0; \ + buf[len] = '\n'; \ + buf[len+1] = 0; \ + return len+1; \ +} \ +static DEVICE_ATTR(name, S_IRUGO, show_##name, NULL); + +usb_intf_str (interface, iInterface); static struct attribute *intf_attrs[] = { &dev_attr_bInterfaceNumber.attr, @@ -240,7 +285,6 @@ static struct attribute *intf_attrs[] = { &dev_attr_bInterfaceClass.attr, &dev_attr_bInterfaceSubClass.attr, &dev_attr_bInterfaceProtocol.attr, - &dev_attr_iInterface.attr, NULL, }; static struct attribute_group intf_attr_grp = { @@ -250,9 +294,17 @@ static struct attribute_group intf_attr_grp = { void usb_create_sysfs_intf_files (struct usb_interface *intf) { sysfs_create_group(&intf->dev.kobj, &intf_attr_grp); + + if (intf->cur_altsetting->desc.iInterface) + device_create_file(&intf->dev, &dev_attr_interface); + } void usb_remove_sysfs_intf_files (struct usb_interface *intf) { sysfs_remove_group(&intf->dev.kobj, &intf_attr_grp); + + if (intf->cur_altsetting->desc.iInterface) + device_remove_file(&intf->dev, &dev_attr_interface); + } -- cgit v1.2.3 From 51876731fb277bbc22eb41dd1c8bbf4ab35af200 Mon Sep 17 00:00:00 2001 From: Nishanth Aravamudan Date: Wed, 29 Sep 2004 02:03:33 -0700 Subject: [PATCH] usb/file_storage: replace schedule_timeout() with msleep_interruptible() Use msleep_interruptible() instead of schedule_timeout() so that the task delays as expected. Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/file_storage.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/file_storage.c b/drivers/usb/gadget/file_storage.c index c0f131f5e235..d8c8ab7750ec 100644 --- a/drivers/usb/gadget/file_storage.c +++ b/drivers/usb/gadget/file_storage.c @@ -217,6 +217,7 @@ #include #include #include +#include #include #include #include @@ -2300,8 +2301,7 @@ static int halt_bulk_in_endpoint(struct fsg_dev *fsg) } /* Wait for a short time and then try again */ - set_current_state(TASK_INTERRUPTIBLE); - if (schedule_timeout(HZ / 10) != 0) + if (msleep_interruptible(100) != 0) return -EINTR; rc = usb_ep_set_halt(fsg->bulk_in); } -- cgit v1.2.3 From ddd63249a3bd56411f374600d0332cd25fb04084 Mon Sep 17 00:00:00 2001 From: Nishanth Aravamudan Date: Wed, 29 Sep 2004 02:04:04 -0700 Subject: [PATCH] usb/ati_remote: add set_current_state() Add set_current_state() before schedule_timeout() so that if the while-loop iterates multiple times, schedule_timeout() delays as expected. Without the addition, schedule_timeout() will return immediately. Signed-off-by: Greg Kroah-Hartman --- drivers/usb/input/ati_remote.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/input/ati_remote.c b/drivers/usb/input/ati_remote.c index 3adbc7bab3c3..91f7f434dd48 100644 --- a/drivers/usb/input/ati_remote.c +++ b/drivers/usb/input/ati_remote.c @@ -418,6 +418,7 @@ static int ati_remote_sendpacket(struct ati_remote *ati_remote, u16 cmd, unsigne while (timeout && (ati_remote->out_urb->status == -EINPROGRESS) && !(ati_remote->send_flags & SEND_FLAG_COMPLETE)) { + set_current_state(TASK_INTERRUPTIBLE); timeout = schedule_timeout(timeout); rmb(); } -- cgit v1.2.3 From 0ae8fe6f9404fbeb6613aa99b2c52c793b45398c Mon Sep 17 00:00:00 2001 From: Nishanth Aravamudan Date: Wed, 29 Sep 2004 02:04:49 -0700 Subject: [PATCH] usb/kaweth: reorder set_current_state() and schedule_timeout() Reorder set_current_state() and schedule_timeout() for a minor cleanup. The reorder allows removing two of the set_current_state() calls. Signed-off-by: Greg Kroah-Hartman --- drivers/usb/net/kaweth.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/usb/net/kaweth.c b/drivers/usb/net/kaweth.c index a84569a43c39..e5e5c649702c 100644 --- a/drivers/usb/net/kaweth.c +++ b/drivers/usb/net/kaweth.c @@ -1250,13 +1250,11 @@ static int usb_start_wait_urb(struct urb *urb, int timeout, int* actual_length) return status; } - set_current_state(TASK_UNINTERRUPTIBLE); while (timeout && !awd.done) { - timeout = schedule_timeout(timeout); set_current_state(TASK_UNINTERRUPTIBLE); + timeout = schedule_timeout(timeout); } - set_current_state(TASK_RUNNING); remove_wait_queue(&awd.wqh, &wait); if (!timeout) { -- cgit v1.2.3 From bd8a148a27cae7667b419540360f2af1c5c6f96b Mon Sep 17 00:00:00 2001 From: Nishanth Aravamudan Date: Wed, 29 Sep 2004 02:05:26 -0700 Subject: [PATCH] usb/hid-core: add set_current_state() before schedule_timeout() Add set_current_state() before schedule_timeout() so that the task delays as expected. Without the addition, schedule_timeout() will return immediately on subsequent iterations of the while-loop. Signed-off-by: Greg Kroah-Hartman --- drivers/usb/input/hid-core.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/input/hid-core.c b/drivers/usb/input/hid-core.c index 4039efd5e7dd..cb0421b04c1e 100644 --- a/drivers/usb/input/hid-core.c +++ b/drivers/usb/input/hid-core.c @@ -1258,8 +1258,10 @@ int hid_wait_io(struct hid_device *hid) add_wait_queue(&hid->wait, &wait); while (timeout && (test_bit(HID_CTRL_RUNNING, &hid->iofl) || - test_bit(HID_OUT_RUNNING, &hid->iofl))) + test_bit(HID_OUT_RUNNING, &hid->iofl))) { + set_current_state(TASK_UNINTERRUPTIBLE); timeout = schedule_timeout(timeout); + } set_current_state(TASK_RUNNING); remove_wait_queue(&hid->wait, &wait); -- cgit v1.2.3 From 6b2d5150fd0e20f449e8a1e55f758386ea72179e Mon Sep 17 00:00:00 2001 From: Nishanth Aravamudan Date: Wed, 29 Sep 2004 02:06:36 -0700 Subject: [PATCH] usb/mdc800: cleanup set_current_state() around wait queues This patch cleans up the wait queue usage in this driver. The state is no longer set until just before the task sleeps, which removes a few set_current_state()s. Correspondingly, the state doesn't need to be set back to TASK_RUNNING outside of the while-loops, as schedule_timeout() takes care of it. Signed-off-by: Greg Kroah-Hartman --- drivers/usb/image/mdc800.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/usb/image/mdc800.c b/drivers/usb/image/mdc800.c index 17e900c198cf..66bb13307df5 100644 --- a/drivers/usb/image/mdc800.c +++ b/drivers/usb/image/mdc800.c @@ -317,7 +317,6 @@ static int mdc800_usb_waitForIRQ (int mode, int msec) mdc800->camera_request_ready=1+mode; add_wait_queue(&mdc800->irq_wait, &wait); - set_current_state(TASK_INTERRUPTIBLE); timeout = msec*HZ/1000; while (!mdc800->irq_woken && timeout) { @@ -325,7 +324,6 @@ static int mdc800_usb_waitForIRQ (int mode, int msec) timeout = schedule_timeout (timeout); } remove_wait_queue(&mdc800->irq_wait, &wait); - set_current_state(TASK_RUNNING); mdc800->irq_woken = 0; if (mdc800->camera_request_ready>0) @@ -725,7 +723,6 @@ static ssize_t mdc800_device_read (struct file *file, char __user *buf, size_t l set_current_state(TASK_UNINTERRUPTIBLE); timeout = schedule_timeout (timeout); } - set_current_state(TASK_RUNNING); remove_wait_queue(&mdc800->download_wait, &wait); mdc800->downloaded = 0; if (mdc800->download_urb->status != 0) @@ -851,7 +848,6 @@ static ssize_t mdc800_device_write (struct file *file, const char __user *buf, s set_current_state(TASK_UNINTERRUPTIBLE); timeout = schedule_timeout (timeout); } - set_current_state(TASK_RUNNING); remove_wait_queue(&mdc800->write_wait, &wait); mdc800->written = 0; if (mdc800->state == WORKING) -- cgit v1.2.3 From 888df350fee33f44385bcd1419b1fa948b07a0b3 Mon Sep 17 00:00:00 2001 From: Nishanth Aravamudan Date: Wed, 29 Sep 2004 02:07:09 -0700 Subject: [PATCH] usb/uss720: replace schedule_timeout() with msleep_interruptible() Use msleep_interruptible() instead of schedule_timeout() to guarantee the task delays as expected. Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/uss720.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/misc/uss720.c b/drivers/usb/misc/uss720.c index 0bc5ccc244ba..8b22320d5ea6 100644 --- a/drivers/usb/misc/uss720.c +++ b/drivers/usb/misc/uss720.c @@ -44,6 +44,7 @@ #include #include #include +#include /* * Version Information @@ -159,8 +160,7 @@ static int change_mode(struct parport *pp, int m) if (time_after_eq (jiffies, expire)) /* The FIFO is stuck. */ return -EBUSY; - set_current_state(TASK_INTERRUPTIBLE); - schedule_timeout((HZ + 99) / 100); + msleep_interruptible(10); if (signal_pending (current)) break; } -- cgit v1.2.3 From ec211cf2c6192f578fe7542db3054ac5af41ea0b Mon Sep 17 00:00:00 2001 From: Jesse Barnes Date: Wed, 29 Sep 2004 02:20:23 -0700 Subject: [PATCH] USB: handle usb host allocation failures gracefully It looks like a host (like ohci or whatever) could try to allocate a new usb_device structure with usb_alloc_dev and get back a valid pointer even if the allocation of its private data failed. I first saw this in the 2.4 sources, but it looks like 2.6 has the same problem. This patch attempts to fix it by freeing dev if the ->allocate() routine fails, and then returns NULL instead of a potentially dangerous dev pointer. Signed-off-by: Jesse Barnes Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/usb.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index c4c457975ae3..2932c62b371f 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -775,7 +775,10 @@ usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port) init_MUTEX(&dev->serialize); if (dev->bus->op->allocate) - dev->bus->op->allocate(dev); + if (dev->bus->op->allocate(dev)) { + kfree(dev); + return NULL; + } return dev; } -- cgit v1.2.3 From cf44e793f222c3ff75f8608e5337dca96b910f1a Mon Sep 17 00:00:00 2001 From: David Brownell Date: Wed, 29 Sep 2004 02:20:47 -0700 Subject: [PATCH] USB: OHCI autodetects "need" for init reset quirk Ther recent QUIRK_INITRESET update turns out to need support for some ALi and ServerWorks chips, as well as the original SiS and OPTi cases. Rather than trying to maintain a quirk table (for what I still think must be a subtle init sequence bug), this patch kicks it in automatically when the frame clock init problem is detected. Signed-off-by: David Brownell Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-hcd.c | 8 ++++++++ drivers/usb/host/ohci-pci.c | 9 --------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index 28f3fdd0751f..09f37cce2c75 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -545,6 +545,7 @@ static int ohci_run (struct ohci_hcd *ohci) /* 2msec timelimit here means no irqs/preempt */ spin_lock_irq (&ohci->lock); +retry: /* HC Reset requires max 10 us delay */ writel (OHCI_HCR, &ohci->regs->cmdstatus); temp = 30; /* ... allow extra time */ @@ -563,6 +564,8 @@ static int ohci_run (struct ohci_hcd *ohci) * ... but some hardware won't init fmInterval "by the book" * (SiS, OPTi ...), so reset again instead. SiS doesn't need * this if we write fmInterval after we're OPERATIONAL. + * Unclear about ALi, ServerWorks, and others ... this could + * easily be a longstanding bug in chip init on Linux. */ if (ohci->flags & OHCI_QUIRK_INITRESET) { writel (ohci->hc_control, &ohci->regs->control); @@ -586,6 +589,11 @@ static int ohci_run (struct ohci_hcd *ohci) */ if ((ohci_readl (&ohci->regs->fminterval) & 0x3fff0000) == 0 || !ohci_readl (&ohci->regs->periodicstart)) { + if (!(ohci->flags & OHCI_QUIRK_INITRESET)) { + ohci->flags |= OHCI_QUIRK_INITRESET; + ohci_dbg (ohci, "enabling initreset quirk\n"); + goto retry; + } spin_unlock_irq (&ohci->lock); ohci_err (ohci, "init err (%08x %04x)\n", ohci_readl (&ohci->regs->fminterval), diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index 23f6b5f3fee5..2211a69e0b9d 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c @@ -69,8 +69,6 @@ ohci_pci_start (struct usb_hcd *hcd) && pdev->device == 0xc861) { ohci_info (ohci, "WARNING: OPTi workarounds unavailable\n"); - /* OPTi sometimes acts wierd during init */ - ohci->flags = OHCI_QUIRK_INITRESET; } /* Check for NSC87560. We have to look at the bridge (fn1) to @@ -88,13 +86,6 @@ ohci_pci_start (struct usb_hcd *hcd) ohci_info (ohci, "Using NSC SuperIO setup\n"); } } - - /* SiS sometimes acts wierd during init */ - else if (pdev->vendor == PCI_VENDOR_ID_SI) { - ohci->flags = OHCI_QUIRK_INITRESET; - ohci_info(ohci, "SiS init quirk\n"); - } - } /* NOTE: there may have already been a first reset, to -- cgit v1.2.3 From 30b44c027f349d38642e963922c5304984517100 Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Wed, 29 Sep 2004 16:34:02 +0100 Subject: NTFS: Implement extent mft record deallocation. Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 5 ++ fs/ntfs/Makefile | 2 +- fs/ntfs/mft.c | 156 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ fs/ntfs/mft.h | 2 + 4 files changed, 164 insertions(+), 1 deletion(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index c175cf4f21a3..9dd0f1236343 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -21,6 +21,11 @@ ToDo/Notes: - Enable the code for setting the NT4 compatibility flag when we start making NTFS 1.2 specific modifications. +2.1.20-WIP + + - Implement extent mft record deallocation + fs/ntfs/mft.c::ntfs_extent_mft_record_free(). + 2.1.19 - Many cleanups, improvements, and a minor bug fix. - Update ->setattr (fs/ntfs/inode.c::ntfs_setattr()) to refuse to diff --git a/fs/ntfs/Makefile b/fs/ntfs/Makefile index 47de3a5da041..744225e8b0c9 100644 --- a/fs/ntfs/Makefile +++ b/fs/ntfs/Makefile @@ -6,7 +6,7 @@ ntfs-objs := aops.o attrib.o collate.o compress.o debug.o dir.o file.o \ index.o inode.o mft.o mst.o namei.o super.o sysctl.o unistr.o \ upcase.o -EXTRA_CFLAGS = -DNTFS_VERSION=\"2.1.19\" +EXTRA_CFLAGS = -DNTFS_VERSION=\"2.1.20-WIP\" ifeq ($(CONFIG_NTFS_DEBUG),y) EXTRA_CFLAGS += -DDEBUG diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 9192bbdf2c2e..ba57d2448eec 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -23,6 +23,7 @@ #include #include "ntfs.h" +#include "bitmap.h" /** * __format_mft_record - initialize an empty mft record @@ -1095,4 +1096,159 @@ static int ntfs_mft_writepage(struct page *page, struct writeback_control *wbc) return 0; } +static const char *es = " Leaving inconsistent metadata. Unmount and run " + "chkdsk."; + +/** + * ntfs_extent_mft_record_free - free an extent mft record on an ntfs volume + * @ni: ntfs inode of the mapped extent mft record to free + * @m: mapped extent mft record of the ntfs inode @ni + * + * Free the mapped extent mft record @m of the extent ntfs inode @ni. + * + * Note that this function unmaps the mft record and closes and destroys @ni + * internally and hence you cannot use either @ni nor @m any more after this + * function returns success. + * + * On success return 0 and on error return -errno. @ni and @m are still valid + * in this case and have not been freed. + * + * For some errors an error message is displayed and the success code 0 is + * returned and the volume is then left dirty on umount. This makes sense in + * case we could not rollback the changes that were already done since the + * caller no longer wants to reference this mft record so it does not matter to + * the caller if something is wrong with it as long as it is properly detached + * from the base inode. + */ +int ntfs_extent_mft_record_free(ntfs_inode *ni, MFT_RECORD *m) +{ + unsigned long mft_no = ni->mft_no; + ntfs_volume *vol = ni->vol; + ntfs_inode *base_ni; + ntfs_inode **extent_nis; + int i, err; + le16 old_seq_no; + u16 seq_no; + + BUG_ON(NInoAttr(ni)); + BUG_ON(ni->nr_extents != -1); + + down(&ni->extent_lock); + base_ni = ni->ext.base_ntfs_ino; + up(&ni->extent_lock); + + BUG_ON(base_ni->nr_extents <= 0); + + ntfs_debug("Entering for extent inode 0x%lx, base inode 0x%lx.\n", + mft_no, base_ni->mft_no); + + down(&base_ni->extent_lock); + + /* Make sure we are holding the only reference to the extent inode. */ + if (atomic_read(&ni->count) > 2) { + ntfs_error(vol->sb, "Tried to free busy extent inode 0x%lx, " + "not freeing.", base_ni->mft_no); + up(&base_ni->extent_lock); + return -EBUSY; + } + + /* Dissociate the ntfs inode from the base inode. */ + extent_nis = base_ni->ext.extent_ntfs_inos; + err = -ENOENT; + for (i = 0; i < base_ni->nr_extents; i++) { + if (ni != extent_nis[i]) + continue; + extent_nis += i; + base_ni->nr_extents--; + memmove(extent_nis, extent_nis + 1, (base_ni->nr_extents - i) * + sizeof(ntfs_inode*)); + err = 0; + break; + } + + up(&base_ni->extent_lock); + + if (unlikely(err)) { + ntfs_error(vol->sb, "Extent inode 0x%lx is not attached to " + "its base inode 0x%lx.", mft_no, + base_ni->mft_no); + BUG(); + } + + /* + * The extent inode is no longer attached to the base inode so no one + * can get a reference to it any more. + */ + + /* Mark the mft record as not in use. */ + m->flags &= const_cpu_to_le16(~const_le16_to_cpu(MFT_RECORD_IN_USE)); + + /* Increment the sequence number, skipping zero, if it is not zero. */ + old_seq_no = m->sequence_number; + seq_no = le16_to_cpu(old_seq_no); + if (seq_no == 0xffff) + seq_no = 1; + else if (seq_no) + seq_no++; + m->sequence_number = cpu_to_le16(seq_no); + + /* + * Set the ntfs inode dirty and write it out. We do not need to worry + * about the base inode here since whatever caused the extent mft + * record to be freed is guaranteed to do it already. + */ + NInoSetDirty(ni); + err = write_mft_record(ni, m, 0); + if (unlikely(err)) { + ntfs_error(vol->sb, "Failed to write mft record 0x%lx, not " + "freeing.", mft_no); + goto rollback; + } +rollback_error: + /* Unmap and throw away the now freed extent inode. */ + unmap_extent_mft_record(ni); + ntfs_clear_extent_inode(ni); + + /* Clear the bit in the $MFT/$BITMAP corresponding to this record. */ + err = ntfs_bitmap_clear_bit(vol->mftbmp_ino, mft_no); + if (unlikely(err)) { + /* + * The extent inode is gone but we failed to deallocate it in + * the mft bitmap. Just emit a warning and leave the volume + * dirty on umount. + */ + ntfs_error(vol->sb, "Failed to clear bit in mft bitmap.%s", es); + NVolSetErrors(vol); + } + return 0; +rollback: + /* Rollback what we did... */ + down(&base_ni->extent_lock); + extent_nis = base_ni->ext.extent_ntfs_inos; + if (!(base_ni->nr_extents & 3)) { + int new_size = (base_ni->nr_extents + 4) * sizeof(ntfs_inode*); + + extent_nis = (ntfs_inode**)kmalloc(new_size, GFP_NOFS); + if (unlikely(!extent_nis)) { + ntfs_error(vol->sb, "Failed to allocate internal " + "buffer during rollback.%s", es); + up(&base_ni->extent_lock); + NVolSetErrors(vol); + goto rollback_error; + } + if (base_ni->nr_extents) { + BUG_ON(!base_ni->ext.extent_ntfs_inos); + memcpy(extent_nis, base_ni->ext.extent_ntfs_inos, + new_size - 4 * sizeof(ntfs_inode*)); + kfree(base_ni->ext.extent_ntfs_inos); + } + base_ni->ext.extent_ntfs_inos = extent_nis; + } + m->flags |= MFT_RECORD_IN_USE; + m->sequence_number = old_seq_no; + extent_nis[base_ni->nr_extents++] = ni; + up(&base_ni->extent_lock); + mark_mft_record_dirty(ni); + return err; +} #endif /* NTFS_RW */ diff --git a/fs/ntfs/mft.h b/fs/ntfs/mft.h index 4fd9b5ec6e0c..95f41e8cd377 100644 --- a/fs/ntfs/mft.h +++ b/fs/ntfs/mft.h @@ -111,6 +111,8 @@ static inline int write_mft_record(ntfs_inode *ni, MFT_RECORD *m, int sync) return err; } +extern int ntfs_extent_mft_record_free(ntfs_inode *ni, MFT_RECORD *m); + #endif /* NTFS_RW */ #endif /* _LINUX_NTFS_MFT_H */ -- cgit v1.2.3 From e2e9540e33f7820f87b560a598b45a12e5f7b78e Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Thu, 30 Sep 2004 11:31:48 +0100 Subject: NTFS: Splitt runlist related functions off from attrib.[hc] to runlist.[hc]. Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 1 + fs/ntfs/Makefile | 4 +- fs/ntfs/attrib.c | 973 +---------------------------------------------------- fs/ntfs/attrib.h | 21 +- fs/ntfs/runlist.c | 986 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ fs/ntfs/runlist.h | 48 +++ 6 files changed, 1046 insertions(+), 987 deletions(-) create mode 100644 fs/ntfs/runlist.c create mode 100644 fs/ntfs/runlist.h diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 9dd0f1236343..2f2d62fc2036 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -25,6 +25,7 @@ ToDo/Notes: - Implement extent mft record deallocation fs/ntfs/mft.c::ntfs_extent_mft_record_free(). + - Splitt runlist related functions off from attrib.[hc] to runlist.[hc]. 2.1.19 - Many cleanups, improvements, and a minor bug fix. diff --git a/fs/ntfs/Makefile b/fs/ntfs/Makefile index 744225e8b0c9..21de52aaddd2 100644 --- a/fs/ntfs/Makefile +++ b/fs/ntfs/Makefile @@ -3,8 +3,8 @@ obj-$(CONFIG_NTFS_FS) += ntfs.o ntfs-objs := aops.o attrib.o collate.o compress.o debug.o dir.o file.o \ - index.o inode.o mft.o mst.o namei.o super.o sysctl.o unistr.o \ - upcase.o + index.o inode.o mft.o mst.o namei.o runlist.o super.o sysctl.o \ + unistr.o upcase.o EXTRA_CFLAGS = -DNTFS_VERSION=\"2.1.20-WIP\" diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 732bfb6cc3c3..6c80b2d1effa 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -1,5 +1,5 @@ /** - * attrib.c - NTFS attribute operations. Part of the Linux-NTFS project. + * attrib.c - NTFS attribute operations. Part of the Linux-NTFS project. * * Copyright (c) 2001-2004 Anton Altaparmakov * Copyright (c) 2002 Richard Russon @@ -22,914 +22,7 @@ #include #include "ntfs.h" -#include "dir.h" - -/* Temporary helper functions -- might become macros */ - -/** - * ntfs_rl_mm - runlist memmove - * - * It is up to the caller to serialize access to the runlist @base. - */ -static inline void ntfs_rl_mm(runlist_element *base, int dst, int src, - int size) -{ - if (likely((dst != src) && (size > 0))) - memmove(base + dst, base + src, size * sizeof (*base)); -} - -/** - * ntfs_rl_mc - runlist memory copy - * - * It is up to the caller to serialize access to the runlists @dstbase and - * @srcbase. - */ -static inline void ntfs_rl_mc(runlist_element *dstbase, int dst, - runlist_element *srcbase, int src, int size) -{ - if (likely(size > 0)) - memcpy(dstbase + dst, srcbase + src, size * sizeof(*dstbase)); -} - -/** - * ntfs_rl_realloc - Reallocate memory for runlists - * @rl: original runlist - * @old_size: number of runlist elements in the original runlist @rl - * @new_size: number of runlist elements we need space for - * - * As the runlists grow, more memory will be required. To prevent the - * kernel having to allocate and reallocate large numbers of small bits of - * memory, this function returns and entire page of memory. - * - * It is up to the caller to serialize access to the runlist @rl. - * - * N.B. If the new allocation doesn't require a different number of pages in - * memory, the function will return the original pointer. - * - * On success, return a pointer to the newly allocated, or recycled, memory. - * On error, return -errno. The following error codes are defined: - * -ENOMEM - Not enough memory to allocate runlist array. - * -EINVAL - Invalid parameters were passed in. - */ -static inline runlist_element *ntfs_rl_realloc(runlist_element *rl, - int old_size, int new_size) -{ - runlist_element *new_rl; - - old_size = PAGE_ALIGN(old_size * sizeof(*rl)); - new_size = PAGE_ALIGN(new_size * sizeof(*rl)); - if (old_size == new_size) - return rl; - - new_rl = ntfs_malloc_nofs(new_size); - if (unlikely(!new_rl)) - return ERR_PTR(-ENOMEM); - - if (likely(rl != NULL)) { - if (unlikely(old_size > new_size)) - old_size = new_size; - memcpy(new_rl, rl, old_size); - ntfs_free(rl); - } - return new_rl; -} - -/** - * ntfs_are_rl_mergeable - test if two runlists can be joined together - * @dst: original runlist - * @src: new runlist to test for mergeability with @dst - * - * Test if two runlists can be joined together. For this, their VCNs and LCNs - * must be adjacent. - * - * It is up to the caller to serialize access to the runlists @dst and @src. - * - * Return: TRUE Success, the runlists can be merged. - * FALSE Failure, the runlists cannot be merged. - */ -static inline BOOL ntfs_are_rl_mergeable(runlist_element *dst, - runlist_element *src) -{ - BUG_ON(!dst); - BUG_ON(!src); - - if ((dst->lcn < 0) || (src->lcn < 0)) /* Are we merging holes? */ - return FALSE; - if ((dst->lcn + dst->length) != src->lcn) /* Are the runs contiguous? */ - return FALSE; - if ((dst->vcn + dst->length) != src->vcn) /* Are the runs misaligned? */ - return FALSE; - - return TRUE; -} - -/** - * __ntfs_rl_merge - merge two runlists without testing if they can be merged - * @dst: original, destination runlist - * @src: new runlist to merge with @dst - * - * Merge the two runlists, writing into the destination runlist @dst. The - * caller must make sure the runlists can be merged or this will corrupt the - * destination runlist. - * - * It is up to the caller to serialize access to the runlists @dst and @src. - */ -static inline void __ntfs_rl_merge(runlist_element *dst, runlist_element *src) -{ - dst->length += src->length; -} - -/** - * ntfs_rl_merge - test if two runlists can be joined together and merge them - * @dst: original, destination runlist - * @src: new runlist to merge with @dst - * - * Test if two runlists can be joined together. For this, their VCNs and LCNs - * must be adjacent. If they can be merged, perform the merge, writing into - * the destination runlist @dst. - * - * It is up to the caller to serialize access to the runlists @dst and @src. - * - * Return: TRUE Success, the runlists have been merged. - * FALSE Failure, the runlists cannot be merged and have not been - * modified. - */ -static inline BOOL ntfs_rl_merge(runlist_element *dst, runlist_element *src) -{ - BOOL merge = ntfs_are_rl_mergeable(dst, src); - - if (merge) - __ntfs_rl_merge(dst, src); - return merge; -} - -/** - * ntfs_rl_append - append a runlist after a given element - * @dst: original runlist to be worked on - * @dsize: number of elements in @dst (including end marker) - * @src: runlist to be inserted into @dst - * @ssize: number of elements in @src (excluding end marker) - * @loc: append the new runlist @src after this element in @dst - * - * Append the runlist @src after element @loc in @dst. Merge the right end of - * the new runlist, if necessary. Adjust the size of the hole before the - * appended runlist. - * - * It is up to the caller to serialize access to the runlists @dst and @src. - * - * On success, return a pointer to the new, combined, runlist. Note, both - * runlists @dst and @src are deallocated before returning so you cannot use - * the pointers for anything any more. (Strictly speaking the returned runlist - * may be the same as @dst but this is irrelevant.) - * - * On error, return -errno. Both runlists are left unmodified. The following - * error codes are defined: - * -ENOMEM - Not enough memory to allocate runlist array. - * -EINVAL - Invalid parameters were passed in. - */ -static inline runlist_element *ntfs_rl_append(runlist_element *dst, - int dsize, runlist_element *src, int ssize, int loc) -{ - BOOL right; - int magic; - - BUG_ON(!dst); - BUG_ON(!src); - - /* First, check if the right hand end needs merging. */ - right = ntfs_are_rl_mergeable(src + ssize - 1, dst + loc + 1); - - /* Space required: @dst size + @src size, less one if we merged. */ - dst = ntfs_rl_realloc(dst, dsize, dsize + ssize - right); - if (IS_ERR(dst)) - return dst; - /* - * We are guaranteed to succeed from here so can start modifying the - * original runlists. - */ - - /* First, merge the right hand end, if necessary. */ - if (right) - __ntfs_rl_merge(src + ssize - 1, dst + loc + 1); - - magic = loc + ssize; - - /* Move the tail of @dst out of the way, then copy in @src. */ - ntfs_rl_mm(dst, magic + 1, loc + 1 + right, dsize - loc - 1 - right); - ntfs_rl_mc(dst, loc + 1, src, 0, ssize); - - /* Adjust the size of the preceding hole. */ - dst[loc].length = dst[loc + 1].vcn - dst[loc].vcn; - - /* We may have changed the length of the file, so fix the end marker */ - if (dst[magic + 1].lcn == LCN_ENOENT) - dst[magic + 1].vcn = dst[magic].vcn + dst[magic].length; - - return dst; -} - -/** - * ntfs_rl_insert - insert a runlist into another - * @dst: original runlist to be worked on - * @dsize: number of elements in @dst (including end marker) - * @src: new runlist to be inserted - * @ssize: number of elements in @src (excluding end marker) - * @loc: insert the new runlist @src before this element in @dst - * - * Insert the runlist @src before element @loc in the runlist @dst. Merge the - * left end of the new runlist, if necessary. Adjust the size of the hole - * after the inserted runlist. - * - * It is up to the caller to serialize access to the runlists @dst and @src. - * - * On success, return a pointer to the new, combined, runlist. Note, both - * runlists @dst and @src are deallocated before returning so you cannot use - * the pointers for anything any more. (Strictly speaking the returned runlist - * may be the same as @dst but this is irrelevant.) - * - * On error, return -errno. Both runlists are left unmodified. The following - * error codes are defined: - * -ENOMEM - Not enough memory to allocate runlist array. - * -EINVAL - Invalid parameters were passed in. - */ -static inline runlist_element *ntfs_rl_insert(runlist_element *dst, - int dsize, runlist_element *src, int ssize, int loc) -{ - BOOL left = FALSE; - BOOL disc = FALSE; /* Discontinuity */ - BOOL hole = FALSE; /* Following a hole */ - int magic; - - BUG_ON(!dst); - BUG_ON(!src); - - /* disc => Discontinuity between the end of @dst and the start of @src. - * This means we might need to insert a hole. - * hole => @dst ends with a hole or an unmapped region which we can - * extend to match the discontinuity. */ - if (loc == 0) - disc = (src[0].vcn > 0); - else { - s64 merged_length; - - left = ntfs_are_rl_mergeable(dst + loc - 1, src); - - merged_length = dst[loc - 1].length; - if (left) - merged_length += src->length; - - disc = (src[0].vcn > dst[loc - 1].vcn + merged_length); - if (disc) - hole = (dst[loc - 1].lcn == LCN_HOLE); - } - - /* Space required: @dst size + @src size, less one if we merged, plus - * one if there was a discontinuity, less one for a trailing hole. */ - dst = ntfs_rl_realloc(dst, dsize, dsize + ssize - left + disc - hole); - if (IS_ERR(dst)) - return dst; - /* - * We are guaranteed to succeed from here so can start modifying the - * original runlist. - */ - - if (left) - __ntfs_rl_merge(dst + loc - 1, src); - - magic = loc + ssize - left + disc - hole; - - /* Move the tail of @dst out of the way, then copy in @src. */ - ntfs_rl_mm(dst, magic, loc, dsize - loc); - ntfs_rl_mc(dst, loc + disc - hole, src, left, ssize - left); - - /* Adjust the VCN of the last run ... */ - if (dst[magic].lcn <= LCN_HOLE) - dst[magic].vcn = dst[magic - 1].vcn + dst[magic - 1].length; - /* ... and the length. */ - if (dst[magic].lcn == LCN_HOLE || dst[magic].lcn == LCN_RL_NOT_MAPPED) - dst[magic].length = dst[magic + 1].vcn - dst[magic].vcn; - - /* Writing beyond the end of the file and there's a discontinuity. */ - if (disc) { - if (hole) - dst[loc - 1].length = dst[loc].vcn - dst[loc - 1].vcn; - else { - if (loc > 0) { - dst[loc].vcn = dst[loc - 1].vcn + - dst[loc - 1].length; - dst[loc].length = dst[loc + 1].vcn - - dst[loc].vcn; - } else { - dst[loc].vcn = 0; - dst[loc].length = dst[loc + 1].vcn; - } - dst[loc].lcn = LCN_RL_NOT_MAPPED; - } - - magic += hole; - - if (dst[magic].lcn == LCN_ENOENT) - dst[magic].vcn = dst[magic - 1].vcn + - dst[magic - 1].length; - } - return dst; -} - -/** - * ntfs_rl_replace - overwrite a runlist element with another runlist - * @dst: original runlist to be worked on - * @dsize: number of elements in @dst (including end marker) - * @src: new runlist to be inserted - * @ssize: number of elements in @src (excluding end marker) - * @loc: index in runlist @dst to overwrite with @src - * - * Replace the runlist element @dst at @loc with @src. Merge the left and - * right ends of the inserted runlist, if necessary. - * - * It is up to the caller to serialize access to the runlists @dst and @src. - * - * On success, return a pointer to the new, combined, runlist. Note, both - * runlists @dst and @src are deallocated before returning so you cannot use - * the pointers for anything any more. (Strictly speaking the returned runlist - * may be the same as @dst but this is irrelevant.) - * - * On error, return -errno. Both runlists are left unmodified. The following - * error codes are defined: - * -ENOMEM - Not enough memory to allocate runlist array. - * -EINVAL - Invalid parameters were passed in. - */ -static inline runlist_element *ntfs_rl_replace(runlist_element *dst, - int dsize, runlist_element *src, int ssize, int loc) -{ - BOOL left = FALSE; - BOOL right; - int magic; - - BUG_ON(!dst); - BUG_ON(!src); - - /* First, merge the left and right ends, if necessary. */ - right = ntfs_are_rl_mergeable(src + ssize - 1, dst + loc + 1); - if (loc > 0) - left = ntfs_are_rl_mergeable(dst + loc - 1, src); - - /* Allocate some space. We'll need less if the left, right, or both - * ends were merged. */ - dst = ntfs_rl_realloc(dst, dsize, dsize + ssize - left - right); - if (IS_ERR(dst)) - return dst; - /* - * We are guaranteed to succeed from here so can start modifying the - * original runlists. - */ - if (right) - __ntfs_rl_merge(src + ssize - 1, dst + loc + 1); - if (left) - __ntfs_rl_merge(dst + loc - 1, src); - - /* FIXME: What does this mean? (AIA) */ - magic = loc + ssize - left; - - /* Move the tail of @dst out of the way, then copy in @src. */ - ntfs_rl_mm(dst, magic, loc + right + 1, dsize - loc - right - 1); - ntfs_rl_mc(dst, loc, src, left, ssize - left); - - /* We may have changed the length of the file, so fix the end marker */ - if (dst[magic].lcn == LCN_ENOENT) - dst[magic].vcn = dst[magic - 1].vcn + dst[magic - 1].length; - return dst; -} - -/** - * ntfs_rl_split - insert a runlist into the centre of a hole - * @dst: original runlist to be worked on - * @dsize: number of elements in @dst (including end marker) - * @src: new runlist to be inserted - * @ssize: number of elements in @src (excluding end marker) - * @loc: index in runlist @dst at which to split and insert @src - * - * Split the runlist @dst at @loc into two and insert @new in between the two - * fragments. No merging of runlists is necessary. Adjust the size of the - * holes either side. - * - * It is up to the caller to serialize access to the runlists @dst and @src. - * - * On success, return a pointer to the new, combined, runlist. Note, both - * runlists @dst and @src are deallocated before returning so you cannot use - * the pointers for anything any more. (Strictly speaking the returned runlist - * may be the same as @dst but this is irrelevant.) - * - * On error, return -errno. Both runlists are left unmodified. The following - * error codes are defined: - * -ENOMEM - Not enough memory to allocate runlist array. - * -EINVAL - Invalid parameters were passed in. - */ -static inline runlist_element *ntfs_rl_split(runlist_element *dst, int dsize, - runlist_element *src, int ssize, int loc) -{ - BUG_ON(!dst); - BUG_ON(!src); - - /* Space required: @dst size + @src size + one new hole. */ - dst = ntfs_rl_realloc(dst, dsize, dsize + ssize + 1); - if (IS_ERR(dst)) - return dst; - /* - * We are guaranteed to succeed from here so can start modifying the - * original runlists. - */ - - /* Move the tail of @dst out of the way, then copy in @src. */ - ntfs_rl_mm(dst, loc + 1 + ssize, loc, dsize - loc); - ntfs_rl_mc(dst, loc + 1, src, 0, ssize); - - /* Adjust the size of the holes either size of @src. */ - dst[loc].length = dst[loc+1].vcn - dst[loc].vcn; - dst[loc+ssize+1].vcn = dst[loc+ssize].vcn + dst[loc+ssize].length; - dst[loc+ssize+1].length = dst[loc+ssize+2].vcn - dst[loc+ssize+1].vcn; - - return dst; -} - -/** - * ntfs_merge_runlists - merge two runlists into one - * @drl: original runlist to be worked on - * @srl: new runlist to be merged into @drl - * - * First we sanity check the two runlists @srl and @drl to make sure that they - * are sensible and can be merged. The runlist @srl must be either after the - * runlist @drl or completely within a hole (or unmapped region) in @drl. - * - * It is up to the caller to serialize access to the runlists @drl and @srl. - * - * Merging of runlists is necessary in two cases: - * 1. When attribute lists are used and a further extent is being mapped. - * 2. When new clusters are allocated to fill a hole or extend a file. - * - * There are four possible ways @srl can be merged. It can: - * - be inserted at the beginning of a hole, - * - split the hole in two and be inserted between the two fragments, - * - be appended at the end of a hole, or it can - * - replace the whole hole. - * It can also be appended to the end of the runlist, which is just a variant - * of the insert case. - * - * On success, return a pointer to the new, combined, runlist. Note, both - * runlists @drl and @srl are deallocated before returning so you cannot use - * the pointers for anything any more. (Strictly speaking the returned runlist - * may be the same as @dst but this is irrelevant.) - * - * On error, return -errno. Both runlists are left unmodified. The following - * error codes are defined: - * -ENOMEM - Not enough memory to allocate runlist array. - * -EINVAL - Invalid parameters were passed in. - * -ERANGE - The runlists overlap and cannot be merged. - */ -runlist_element *ntfs_merge_runlists(runlist_element *drl, - runlist_element *srl) -{ - int di, si; /* Current index into @[ds]rl. */ - int sstart; /* First index with lcn > LCN_RL_NOT_MAPPED. */ - int dins; /* Index into @drl at which to insert @srl. */ - int dend, send; /* Last index into @[ds]rl. */ - int dfinal, sfinal; /* The last index into @[ds]rl with - lcn >= LCN_HOLE. */ - int marker = 0; - VCN marker_vcn = 0; - -#ifdef DEBUG - ntfs_debug("dst:"); - ntfs_debug_dump_runlist(drl); - ntfs_debug("src:"); - ntfs_debug_dump_runlist(srl); -#endif - - /* Check for silly calling... */ - if (unlikely(!srl)) - return drl; - if (IS_ERR(srl) || IS_ERR(drl)) - return ERR_PTR(-EINVAL); - - /* Check for the case where the first mapping is being done now. */ - if (unlikely(!drl)) { - drl = srl; - /* Complete the source runlist if necessary. */ - if (unlikely(drl[0].vcn)) { - /* Scan to the end of the source runlist. */ - for (dend = 0; likely(drl[dend].length); dend++) - ; - drl = ntfs_rl_realloc(drl, dend, dend + 1); - if (IS_ERR(drl)) - return drl; - /* Insert start element at the front of the runlist. */ - ntfs_rl_mm(drl, 1, 0, dend); - drl[0].vcn = 0; - drl[0].lcn = LCN_RL_NOT_MAPPED; - drl[0].length = drl[1].vcn; - } - goto finished; - } - - si = di = 0; - - /* Skip any unmapped start element(s) in the source runlist. */ - while (srl[si].length && srl[si].lcn < (LCN)LCN_HOLE) - si++; - - /* Can't have an entirely unmapped source runlist. */ - BUG_ON(!srl[si].length); - - /* Record the starting points. */ - sstart = si; - - /* - * Skip forward in @drl until we reach the position where @srl needs to - * be inserted. If we reach the end of @drl, @srl just needs to be - * appended to @drl. - */ - for (; drl[di].length; di++) { - if (drl[di].vcn + drl[di].length > srl[sstart].vcn) - break; - } - dins = di; - - /* Sanity check for illegal overlaps. */ - if ((drl[di].vcn == srl[si].vcn) && (drl[di].lcn >= 0) && - (srl[si].lcn >= 0)) { - ntfs_error(NULL, "Run lists overlap. Cannot merge!"); - return ERR_PTR(-ERANGE); - } - - /* Scan to the end of both runlists in order to know their sizes. */ - for (send = si; srl[send].length; send++) - ; - for (dend = di; drl[dend].length; dend++) - ; - - if (srl[send].lcn == (LCN)LCN_ENOENT) - marker_vcn = srl[marker = send].vcn; - - /* Scan to the last element with lcn >= LCN_HOLE. */ - for (sfinal = send; sfinal >= 0 && srl[sfinal].lcn < LCN_HOLE; sfinal--) - ; - for (dfinal = dend; dfinal >= 0 && drl[dfinal].lcn < LCN_HOLE; dfinal--) - ; - - { - BOOL start; - BOOL finish; - int ds = dend + 1; /* Number of elements in drl & srl */ - int ss = sfinal - sstart + 1; - - start = ((drl[dins].lcn < LCN_RL_NOT_MAPPED) || /* End of file */ - (drl[dins].vcn == srl[sstart].vcn)); /* Start of hole */ - finish = ((drl[dins].lcn >= LCN_RL_NOT_MAPPED) && /* End of file */ - ((drl[dins].vcn + drl[dins].length) <= /* End of hole */ - (srl[send - 1].vcn + srl[send - 1].length))); - - /* Or we'll lose an end marker */ - if (start && finish && (drl[dins].length == 0)) - ss++; - if (marker && (drl[dins].vcn + drl[dins].length > srl[send - 1].vcn)) - finish = FALSE; -#if 0 - ntfs_debug("dfinal = %i, dend = %i", dfinal, dend); - ntfs_debug("sstart = %i, sfinal = %i, send = %i", sstart, sfinal, send); - ntfs_debug("start = %i, finish = %i", start, finish); - ntfs_debug("ds = %i, ss = %i, dins = %i", ds, ss, dins); -#endif - if (start) { - if (finish) - drl = ntfs_rl_replace(drl, ds, srl + sstart, ss, dins); - else - drl = ntfs_rl_insert(drl, ds, srl + sstart, ss, dins); - } else { - if (finish) - drl = ntfs_rl_append(drl, ds, srl + sstart, ss, dins); - else - drl = ntfs_rl_split(drl, ds, srl + sstart, ss, dins); - } - if (IS_ERR(drl)) { - ntfs_error(NULL, "Merge failed."); - return drl; - } - ntfs_free(srl); - if (marker) { - ntfs_debug("Triggering marker code."); - for (ds = dend; drl[ds].length; ds++) - ; - /* We only need to care if @srl ended after @drl. */ - if (drl[ds].vcn <= marker_vcn) { - int slots = 0; - - if (drl[ds].vcn == marker_vcn) { - ntfs_debug("Old marker = 0x%llx, replacing " - "with LCN_ENOENT.", - (unsigned long long) - drl[ds].lcn); - drl[ds].lcn = (LCN)LCN_ENOENT; - goto finished; - } - /* - * We need to create an unmapped runlist element in - * @drl or extend an existing one before adding the - * ENOENT terminator. - */ - if (drl[ds].lcn == (LCN)LCN_ENOENT) { - ds--; - slots = 1; - } - if (drl[ds].lcn != (LCN)LCN_RL_NOT_MAPPED) { - /* Add an unmapped runlist element. */ - if (!slots) { - /* FIXME/TODO: We need to have the - * extra memory already! (AIA) */ - drl = ntfs_rl_realloc(drl, ds, ds + 2); - if (!drl) - goto critical_error; - slots = 2; - } - ds++; - /* Need to set vcn if it isn't set already. */ - if (slots != 1) - drl[ds].vcn = drl[ds - 1].vcn + - drl[ds - 1].length; - drl[ds].lcn = (LCN)LCN_RL_NOT_MAPPED; - /* We now used up a slot. */ - slots--; - } - drl[ds].length = marker_vcn - drl[ds].vcn; - /* Finally add the ENOENT terminator. */ - ds++; - if (!slots) { - /* FIXME/TODO: We need to have the extra - * memory already! (AIA) */ - drl = ntfs_rl_realloc(drl, ds, ds + 1); - if (!drl) - goto critical_error; - } - drl[ds].vcn = marker_vcn; - drl[ds].lcn = (LCN)LCN_ENOENT; - drl[ds].length = (s64)0; - } - } - } - -finished: - /* The merge was completed successfully. */ - ntfs_debug("Merged runlist:"); - ntfs_debug_dump_runlist(drl); - return drl; - -critical_error: - /* Critical error! We cannot afford to fail here. */ - ntfs_error(NULL, "Critical error! Not enough memory."); - panic("NTFS: Cannot continue."); -} - -/** - * decompress_mapping_pairs - convert mapping pairs array to runlist - * @vol: ntfs volume on which the attribute resides - * @attr: attribute record whose mapping pairs array to decompress - * @old_rl: optional runlist in which to insert @attr's runlist - * - * It is up to the caller to serialize access to the runlist @old_rl. - * - * Decompress the attribute @attr's mapping pairs array into a runlist. On - * success, return the decompressed runlist. - * - * If @old_rl is not NULL, decompressed runlist is inserted into the - * appropriate place in @old_rl and the resultant, combined runlist is - * returned. The original @old_rl is deallocated. - * - * On error, return -errno. @old_rl is left unmodified in that case. - * - * The following error codes are defined: - * -ENOMEM - Not enough memory to allocate runlist array. - * -EIO - Corrupt runlist. - * -EINVAL - Invalid parameters were passed in. - * -ERANGE - The two runlists overlap. - * - * FIXME: For now we take the conceptionally simplest approach of creating the - * new runlist disregarding the already existing one and then splicing the - * two into one, if that is possible (we check for overlap and discard the new - * runlist if overlap present before returning ERR_PTR(-ERANGE)). - */ -runlist_element *decompress_mapping_pairs(const ntfs_volume *vol, - const ATTR_RECORD *attr, runlist_element *old_rl) -{ - VCN vcn; /* Current vcn. */ - LCN lcn; /* Current lcn. */ - s64 deltaxcn; /* Change in [vl]cn. */ - runlist_element *rl; /* The output runlist. */ - u8 *buf; /* Current position in mapping pairs array. */ - u8 *attr_end; /* End of attribute. */ - int rlsize; /* Size of runlist buffer. */ - u16 rlpos; /* Current runlist position in units of - runlist_elements. */ - u8 b; /* Current byte offset in buf. */ - -#ifdef DEBUG - /* Make sure attr exists and is non-resident. */ - if (!attr || !attr->non_resident || sle64_to_cpu( - attr->data.non_resident.lowest_vcn) < (VCN)0) { - ntfs_error(vol->sb, "Invalid arguments."); - return ERR_PTR(-EINVAL); - } -#endif - /* Start at vcn = lowest_vcn and lcn 0. */ - vcn = sle64_to_cpu(attr->data.non_resident.lowest_vcn); - lcn = 0; - /* Get start of the mapping pairs array. */ - buf = (u8*)attr + le16_to_cpu( - attr->data.non_resident.mapping_pairs_offset); - attr_end = (u8*)attr + le32_to_cpu(attr->length); - if (unlikely(buf < (u8*)attr || buf > attr_end)) { - ntfs_error(vol->sb, "Corrupt attribute."); - return ERR_PTR(-EIO); - } - /* Current position in runlist array. */ - rlpos = 0; - /* Allocate first page and set current runlist size to one page. */ - rl = ntfs_malloc_nofs(rlsize = PAGE_SIZE); - if (unlikely(!rl)) - return ERR_PTR(-ENOMEM); - /* Insert unmapped starting element if necessary. */ - if (vcn) { - rl->vcn = (VCN)0; - rl->lcn = (LCN)LCN_RL_NOT_MAPPED; - rl->length = vcn; - rlpos++; - } - while (buf < attr_end && *buf) { - /* - * Allocate more memory if needed, including space for the - * not-mapped and terminator elements. ntfs_malloc_nofs() - * operates on whole pages only. - */ - if (((rlpos + 3) * sizeof(*old_rl)) > rlsize) { - runlist_element *rl2; - - rl2 = ntfs_malloc_nofs(rlsize + (int)PAGE_SIZE); - if (unlikely(!rl2)) { - ntfs_free(rl); - return ERR_PTR(-ENOMEM); - } - memcpy(rl2, rl, rlsize); - ntfs_free(rl); - rl = rl2; - rlsize += PAGE_SIZE; - } - /* Enter the current vcn into the current runlist element. */ - rl[rlpos].vcn = vcn; - /* - * Get the change in vcn, i.e. the run length in clusters. - * Doing it this way ensures that we signextend negative values. - * A negative run length doesn't make any sense, but hey, I - * didn't make up the NTFS specs and Windows NT4 treats the run - * length as a signed value so that's how it is... - */ - b = *buf & 0xf; - if (b) { - if (unlikely(buf + b > attr_end)) - goto io_error; - for (deltaxcn = (s8)buf[b--]; b; b--) - deltaxcn = (deltaxcn << 8) + buf[b]; - } else { /* The length entry is compulsory. */ - ntfs_error(vol->sb, "Missing length entry in mapping " - "pairs array."); - deltaxcn = (s64)-1; - } - /* - * Assume a negative length to indicate data corruption and - * hence clean-up and return NULL. - */ - if (unlikely(deltaxcn < 0)) { - ntfs_error(vol->sb, "Invalid length in mapping pairs " - "array."); - goto err_out; - } - /* - * Enter the current run length into the current runlist - * element. - */ - rl[rlpos].length = deltaxcn; - /* Increment the current vcn by the current run length. */ - vcn += deltaxcn; - /* - * There might be no lcn change at all, as is the case for - * sparse clusters on NTFS 3.0+, in which case we set the lcn - * to LCN_HOLE. - */ - if (!(*buf & 0xf0)) - rl[rlpos].lcn = (LCN)LCN_HOLE; - else { - /* Get the lcn change which really can be negative. */ - u8 b2 = *buf & 0xf; - b = b2 + ((*buf >> 4) & 0xf); - if (buf + b > attr_end) - goto io_error; - for (deltaxcn = (s8)buf[b--]; b > b2; b--) - deltaxcn = (deltaxcn << 8) + buf[b]; - /* Change the current lcn to its new value. */ - lcn += deltaxcn; -#ifdef DEBUG - /* - * On NTFS 1.2-, apparently can have lcn == -1 to - * indicate a hole. But we haven't verified ourselves - * whether it is really the lcn or the deltaxcn that is - * -1. So if either is found give us a message so we - * can investigate it further! - */ - if (vol->major_ver < 3) { - if (unlikely(deltaxcn == (LCN)-1)) - ntfs_error(vol->sb, "lcn delta == -1"); - if (unlikely(lcn == (LCN)-1)) - ntfs_error(vol->sb, "lcn == -1"); - } -#endif - /* Check lcn is not below -1. */ - if (unlikely(lcn < (LCN)-1)) { - ntfs_error(vol->sb, "Invalid LCN < -1 in " - "mapping pairs array."); - goto err_out; - } - /* Enter the current lcn into the runlist element. */ - rl[rlpos].lcn = lcn; - } - /* Get to the next runlist element. */ - rlpos++; - /* Increment the buffer position to the next mapping pair. */ - buf += (*buf & 0xf) + ((*buf >> 4) & 0xf) + 1; - } - if (unlikely(buf >= attr_end)) - goto io_error; - /* - * If there is a highest_vcn specified, it must be equal to the final - * vcn in the runlist - 1, or something has gone badly wrong. - */ - deltaxcn = sle64_to_cpu(attr->data.non_resident.highest_vcn); - if (unlikely(deltaxcn && vcn - 1 != deltaxcn)) { -mpa_err: - ntfs_error(vol->sb, "Corrupt mapping pairs array in " - "non-resident attribute."); - goto err_out; - } - /* Setup not mapped runlist element if this is the base extent. */ - if (!attr->data.non_resident.lowest_vcn) { - VCN max_cluster; - - max_cluster = (sle64_to_cpu( - attr->data.non_resident.allocated_size) + - vol->cluster_size - 1) >> - vol->cluster_size_bits; - /* - * If there is a difference between the highest_vcn and the - * highest cluster, the runlist is either corrupt or, more - * likely, there are more extents following this one. - */ - if (deltaxcn < --max_cluster) { - ntfs_debug("More extents to follow; deltaxcn = 0x%llx, " - "max_cluster = 0x%llx", - (unsigned long long)deltaxcn, - (unsigned long long)max_cluster); - rl[rlpos].vcn = vcn; - vcn += rl[rlpos].length = max_cluster - deltaxcn; - rl[rlpos].lcn = (LCN)LCN_RL_NOT_MAPPED; - rlpos++; - } else if (unlikely(deltaxcn > max_cluster)) { - ntfs_error(vol->sb, "Corrupt attribute. deltaxcn = " - "0x%llx, max_cluster = 0x%llx", - (unsigned long long)deltaxcn, - (unsigned long long)max_cluster); - goto mpa_err; - } - rl[rlpos].lcn = (LCN)LCN_ENOENT; - } else /* Not the base extent. There may be more extents to follow. */ - rl[rlpos].lcn = (LCN)LCN_RL_NOT_MAPPED; - - /* Setup terminating runlist element. */ - rl[rlpos].vcn = vcn; - rl[rlpos].length = (s64)0; - /* If no existing runlist was specified, we are done. */ - if (!old_rl) { - ntfs_debug("Mapping pairs array successfully decompressed:"); - ntfs_debug_dump_runlist(rl); - return rl; - } - /* Now combine the new and old runlists checking for overlaps. */ - old_rl = ntfs_merge_runlists(old_rl, rl); - if (likely(!IS_ERR(old_rl))) - return old_rl; - ntfs_free(rl); - ntfs_error(vol->sb, "Failed to merge runlists."); - return old_rl; -io_error: - ntfs_error(vol->sb, "Corrupt attribute."); -err_out: - ntfs_free(rl); - return ERR_PTR(-EIO); -} +//#include "dir.h" /** * ntfs_map_runlist - map (a part of) a runlist of an ntfs inode @@ -992,63 +85,6 @@ err_out: return err; } -/** - * ntfs_vcn_to_lcn - convert a vcn into a lcn given a runlist - * @rl: runlist to use for conversion - * @vcn: vcn to convert - * - * Convert the virtual cluster number @vcn of an attribute into a logical - * cluster number (lcn) of a device using the runlist @rl to map vcns to their - * corresponding lcns. - * - * It is up to the caller to serialize access to the runlist @rl. - * - * Since lcns must be >= 0, we use negative return values with special meaning: - * - * Return value Meaning / Description - * ================================================== - * -1 = LCN_HOLE Hole / not allocated on disk. - * -2 = LCN_RL_NOT_MAPPED This is part of the runlist which has not been - * inserted into the runlist yet. - * -3 = LCN_ENOENT There is no such vcn in the attribute. - * - * Locking: - The caller must have locked the runlist (for reading or writing). - * - This function does not touch the lock. - */ -LCN ntfs_vcn_to_lcn(const runlist_element *rl, const VCN vcn) -{ - int i; - - BUG_ON(vcn < 0); - /* - * If rl is NULL, assume that we have found an unmapped runlist. The - * caller can then attempt to map it and fail appropriately if - * necessary. - */ - if (unlikely(!rl)) - return (LCN)LCN_RL_NOT_MAPPED; - - /* Catch out of lower bounds vcn. */ - if (unlikely(vcn < rl[0].vcn)) - return (LCN)LCN_ENOENT; - - for (i = 0; likely(rl[i].length); i++) { - if (unlikely(vcn < rl[i+1].vcn)) { - if (likely(rl[i].lcn >= (LCN)0)) - return rl[i].lcn + (vcn - rl[i].vcn); - return rl[i].lcn; - } - } - /* - * The terminator element is setup to the correct value, i.e. one of - * LCN_HOLE, LCN_RL_NOT_MAPPED, or LCN_ENOENT. - */ - if (likely(rl[i].lcn < (LCN)0)) - return rl[i].lcn; - /* Just in case... We could replace this with BUG() some day. */ - return (LCN)LCN_ENOENT; -} - /** * ntfs_find_vcn - find a vcn in the runlist described by an ntfs inode * @ni: ntfs inode describing the runlist to search @@ -1861,6 +897,11 @@ void ntfs_attr_reinit_search_ctx(ntfs_attr_search_ctx *ctx) /* Sanity checks are performed elsewhere. */ ctx->attr = (ATTR_RECORD*)((u8*)ctx->mrec + le16_to_cpu(ctx->mrec->attrs_offset)); + /* + * This needs resetting due to ntfs_external_attr_find() which + * can leave it set despite having zeroed ctx->base_ntfs_ino. + */ + ctx->al_entry = NULL; return; } /* Attribute list. */ if (ctx->ntfs_ino != ctx->base_ntfs_ino) diff --git a/fs/ntfs/attrib.h b/fs/ntfs/attrib.h index 92899f4ff571..16a5eca832de 100644 --- a/fs/ntfs/attrib.h +++ b/fs/ntfs/attrib.h @@ -24,23 +24,11 @@ #ifndef _LINUX_NTFS_ATTRIB_H #define _LINUX_NTFS_ATTRIB_H -#include - #include "endian.h" #include "types.h" #include "layout.h" - -static inline void init_runlist(runlist *rl) -{ - rl->rl = NULL; - init_rwsem(&rl->lock); -} - -typedef enum { - LCN_HOLE = -1, /* Keep this as highest value or die! */ - LCN_RL_NOT_MAPPED = -2, - LCN_ENOENT = -3, -} LCN_SPECIAL_VALUES; +#include "inode.h" +#include "runlist.h" /** * ntfs_attr_search_ctx - used in attribute search functions @@ -71,13 +59,8 @@ typedef struct { ATTR_RECORD *base_attr; } ntfs_attr_search_ctx; -extern runlist_element *decompress_mapping_pairs(const ntfs_volume *vol, - const ATTR_RECORD *attr, runlist_element *old_rl); - extern int ntfs_map_runlist(ntfs_inode *ni, VCN vcn); -extern LCN ntfs_vcn_to_lcn(const runlist_element *rl, const VCN vcn); - extern runlist_element *ntfs_find_vcn(ntfs_inode *ni, const VCN vcn, const BOOL need_write); diff --git a/fs/ntfs/runlist.c b/fs/ntfs/runlist.c new file mode 100644 index 000000000000..f28d054691a9 --- /dev/null +++ b/fs/ntfs/runlist.c @@ -0,0 +1,986 @@ +/** + * runlist.c - NTFS runlist handling code. Part of the Linux-NTFS project. + * + * Copyright (c) 2001-2004 Anton Altaparmakov + * Copyright (c) 2002 Richard Russon + * + * This program/include file is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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. + * + * You should have received a copy of the GNU General Public License + * along with this program (in the main directory of the Linux-NTFS + * distribution in the file COPYING); if not, write to the Free Software + * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "ntfs.h" +#include "dir.h" + +/** + * ntfs_rl_mm - runlist memmove + * + * It is up to the caller to serialize access to the runlist @base. + */ +static inline void ntfs_rl_mm(runlist_element *base, int dst, int src, + int size) +{ + if (likely((dst != src) && (size > 0))) + memmove(base + dst, base + src, size * sizeof (*base)); +} + +/** + * ntfs_rl_mc - runlist memory copy + * + * It is up to the caller to serialize access to the runlists @dstbase and + * @srcbase. + */ +static inline void ntfs_rl_mc(runlist_element *dstbase, int dst, + runlist_element *srcbase, int src, int size) +{ + if (likely(size > 0)) + memcpy(dstbase + dst, srcbase + src, size * sizeof(*dstbase)); +} + +/** + * ntfs_rl_realloc - Reallocate memory for runlists + * @rl: original runlist + * @old_size: number of runlist elements in the original runlist @rl + * @new_size: number of runlist elements we need space for + * + * As the runlists grow, more memory will be required. To prevent the + * kernel having to allocate and reallocate large numbers of small bits of + * memory, this function returns and entire page of memory. + * + * It is up to the caller to serialize access to the runlist @rl. + * + * N.B. If the new allocation doesn't require a different number of pages in + * memory, the function will return the original pointer. + * + * On success, return a pointer to the newly allocated, or recycled, memory. + * On error, return -errno. The following error codes are defined: + * -ENOMEM - Not enough memory to allocate runlist array. + * -EINVAL - Invalid parameters were passed in. + */ +static inline runlist_element *ntfs_rl_realloc(runlist_element *rl, + int old_size, int new_size) +{ + runlist_element *new_rl; + + old_size = PAGE_ALIGN(old_size * sizeof(*rl)); + new_size = PAGE_ALIGN(new_size * sizeof(*rl)); + if (old_size == new_size) + return rl; + + new_rl = ntfs_malloc_nofs(new_size); + if (unlikely(!new_rl)) + return ERR_PTR(-ENOMEM); + + if (likely(rl != NULL)) { + if (unlikely(old_size > new_size)) + old_size = new_size; + memcpy(new_rl, rl, old_size); + ntfs_free(rl); + } + return new_rl; +} + +/** + * ntfs_are_rl_mergeable - test if two runlists can be joined together + * @dst: original runlist + * @src: new runlist to test for mergeability with @dst + * + * Test if two runlists can be joined together. For this, their VCNs and LCNs + * must be adjacent. + * + * It is up to the caller to serialize access to the runlists @dst and @src. + * + * Return: TRUE Success, the runlists can be merged. + * FALSE Failure, the runlists cannot be merged. + */ +static inline BOOL ntfs_are_rl_mergeable(runlist_element *dst, + runlist_element *src) +{ + BUG_ON(!dst); + BUG_ON(!src); + + if ((dst->lcn < 0) || (src->lcn < 0)) /* Are we merging holes? */ + return FALSE; + if ((dst->lcn + dst->length) != src->lcn) /* Are the runs contiguous? */ + return FALSE; + if ((dst->vcn + dst->length) != src->vcn) /* Are the runs misaligned? */ + return FALSE; + + return TRUE; +} + +/** + * __ntfs_rl_merge - merge two runlists without testing if they can be merged + * @dst: original, destination runlist + * @src: new runlist to merge with @dst + * + * Merge the two runlists, writing into the destination runlist @dst. The + * caller must make sure the runlists can be merged or this will corrupt the + * destination runlist. + * + * It is up to the caller to serialize access to the runlists @dst and @src. + */ +static inline void __ntfs_rl_merge(runlist_element *dst, runlist_element *src) +{ + dst->length += src->length; +} + +/** + * ntfs_rl_merge - test if two runlists can be joined together and merge them + * @dst: original, destination runlist + * @src: new runlist to merge with @dst + * + * Test if two runlists can be joined together. For this, their VCNs and LCNs + * must be adjacent. If they can be merged, perform the merge, writing into + * the destination runlist @dst. + * + * It is up to the caller to serialize access to the runlists @dst and @src. + * + * Return: TRUE Success, the runlists have been merged. + * FALSE Failure, the runlists cannot be merged and have not been + * modified. + */ +static inline BOOL ntfs_rl_merge(runlist_element *dst, runlist_element *src) +{ + BOOL merge = ntfs_are_rl_mergeable(dst, src); + + if (merge) + __ntfs_rl_merge(dst, src); + return merge; +} + +/** + * ntfs_rl_append - append a runlist after a given element + * @dst: original runlist to be worked on + * @dsize: number of elements in @dst (including end marker) + * @src: runlist to be inserted into @dst + * @ssize: number of elements in @src (excluding end marker) + * @loc: append the new runlist @src after this element in @dst + * + * Append the runlist @src after element @loc in @dst. Merge the right end of + * the new runlist, if necessary. Adjust the size of the hole before the + * appended runlist. + * + * It is up to the caller to serialize access to the runlists @dst and @src. + * + * On success, return a pointer to the new, combined, runlist. Note, both + * runlists @dst and @src are deallocated before returning so you cannot use + * the pointers for anything any more. (Strictly speaking the returned runlist + * may be the same as @dst but this is irrelevant.) + * + * On error, return -errno. Both runlists are left unmodified. The following + * error codes are defined: + * -ENOMEM - Not enough memory to allocate runlist array. + * -EINVAL - Invalid parameters were passed in. + */ +static inline runlist_element *ntfs_rl_append(runlist_element *dst, + int dsize, runlist_element *src, int ssize, int loc) +{ + BOOL right; + int magic; + + BUG_ON(!dst); + BUG_ON(!src); + + /* First, check if the right hand end needs merging. */ + right = ntfs_are_rl_mergeable(src + ssize - 1, dst + loc + 1); + + /* Space required: @dst size + @src size, less one if we merged. */ + dst = ntfs_rl_realloc(dst, dsize, dsize + ssize - right); + if (IS_ERR(dst)) + return dst; + /* + * We are guaranteed to succeed from here so can start modifying the + * original runlists. + */ + + /* First, merge the right hand end, if necessary. */ + if (right) + __ntfs_rl_merge(src + ssize - 1, dst + loc + 1); + + magic = loc + ssize; + + /* Move the tail of @dst out of the way, then copy in @src. */ + ntfs_rl_mm(dst, magic + 1, loc + 1 + right, dsize - loc - 1 - right); + ntfs_rl_mc(dst, loc + 1, src, 0, ssize); + + /* Adjust the size of the preceding hole. */ + dst[loc].length = dst[loc + 1].vcn - dst[loc].vcn; + + /* We may have changed the length of the file, so fix the end marker */ + if (dst[magic + 1].lcn == LCN_ENOENT) + dst[magic + 1].vcn = dst[magic].vcn + dst[magic].length; + + return dst; +} + +/** + * ntfs_rl_insert - insert a runlist into another + * @dst: original runlist to be worked on + * @dsize: number of elements in @dst (including end marker) + * @src: new runlist to be inserted + * @ssize: number of elements in @src (excluding end marker) + * @loc: insert the new runlist @src before this element in @dst + * + * Insert the runlist @src before element @loc in the runlist @dst. Merge the + * left end of the new runlist, if necessary. Adjust the size of the hole + * after the inserted runlist. + * + * It is up to the caller to serialize access to the runlists @dst and @src. + * + * On success, return a pointer to the new, combined, runlist. Note, both + * runlists @dst and @src are deallocated before returning so you cannot use + * the pointers for anything any more. (Strictly speaking the returned runlist + * may be the same as @dst but this is irrelevant.) + * + * On error, return -errno. Both runlists are left unmodified. The following + * error codes are defined: + * -ENOMEM - Not enough memory to allocate runlist array. + * -EINVAL - Invalid parameters were passed in. + */ +static inline runlist_element *ntfs_rl_insert(runlist_element *dst, + int dsize, runlist_element *src, int ssize, int loc) +{ + BOOL left = FALSE; + BOOL disc = FALSE; /* Discontinuity */ + BOOL hole = FALSE; /* Following a hole */ + int magic; + + BUG_ON(!dst); + BUG_ON(!src); + + /* disc => Discontinuity between the end of @dst and the start of @src. + * This means we might need to insert a hole. + * hole => @dst ends with a hole or an unmapped region which we can + * extend to match the discontinuity. */ + if (loc == 0) + disc = (src[0].vcn > 0); + else { + s64 merged_length; + + left = ntfs_are_rl_mergeable(dst + loc - 1, src); + + merged_length = dst[loc - 1].length; + if (left) + merged_length += src->length; + + disc = (src[0].vcn > dst[loc - 1].vcn + merged_length); + if (disc) + hole = (dst[loc - 1].lcn == LCN_HOLE); + } + + /* Space required: @dst size + @src size, less one if we merged, plus + * one if there was a discontinuity, less one for a trailing hole. */ + dst = ntfs_rl_realloc(dst, dsize, dsize + ssize - left + disc - hole); + if (IS_ERR(dst)) + return dst; + /* + * We are guaranteed to succeed from here so can start modifying the + * original runlist. + */ + + if (left) + __ntfs_rl_merge(dst + loc - 1, src); + + magic = loc + ssize - left + disc - hole; + + /* Move the tail of @dst out of the way, then copy in @src. */ + ntfs_rl_mm(dst, magic, loc, dsize - loc); + ntfs_rl_mc(dst, loc + disc - hole, src, left, ssize - left); + + /* Adjust the VCN of the last run ... */ + if (dst[magic].lcn <= LCN_HOLE) + dst[magic].vcn = dst[magic - 1].vcn + dst[magic - 1].length; + /* ... and the length. */ + if (dst[magic].lcn == LCN_HOLE || dst[magic].lcn == LCN_RL_NOT_MAPPED) + dst[magic].length = dst[magic + 1].vcn - dst[magic].vcn; + + /* Writing beyond the end of the file and there's a discontinuity. */ + if (disc) { + if (hole) + dst[loc - 1].length = dst[loc].vcn - dst[loc - 1].vcn; + else { + if (loc > 0) { + dst[loc].vcn = dst[loc - 1].vcn + + dst[loc - 1].length; + dst[loc].length = dst[loc + 1].vcn - + dst[loc].vcn; + } else { + dst[loc].vcn = 0; + dst[loc].length = dst[loc + 1].vcn; + } + dst[loc].lcn = LCN_RL_NOT_MAPPED; + } + + magic += hole; + + if (dst[magic].lcn == LCN_ENOENT) + dst[magic].vcn = dst[magic - 1].vcn + + dst[magic - 1].length; + } + return dst; +} + +/** + * ntfs_rl_replace - overwrite a runlist element with another runlist + * @dst: original runlist to be worked on + * @dsize: number of elements in @dst (including end marker) + * @src: new runlist to be inserted + * @ssize: number of elements in @src (excluding end marker) + * @loc: index in runlist @dst to overwrite with @src + * + * Replace the runlist element @dst at @loc with @src. Merge the left and + * right ends of the inserted runlist, if necessary. + * + * It is up to the caller to serialize access to the runlists @dst and @src. + * + * On success, return a pointer to the new, combined, runlist. Note, both + * runlists @dst and @src are deallocated before returning so you cannot use + * the pointers for anything any more. (Strictly speaking the returned runlist + * may be the same as @dst but this is irrelevant.) + * + * On error, return -errno. Both runlists are left unmodified. The following + * error codes are defined: + * -ENOMEM - Not enough memory to allocate runlist array. + * -EINVAL - Invalid parameters were passed in. + */ +static inline runlist_element *ntfs_rl_replace(runlist_element *dst, + int dsize, runlist_element *src, int ssize, int loc) +{ + BOOL left = FALSE; + BOOL right; + int magic; + + BUG_ON(!dst); + BUG_ON(!src); + + /* First, merge the left and right ends, if necessary. */ + right = ntfs_are_rl_mergeable(src + ssize - 1, dst + loc + 1); + if (loc > 0) + left = ntfs_are_rl_mergeable(dst + loc - 1, src); + + /* Allocate some space. We'll need less if the left, right, or both + * ends were merged. */ + dst = ntfs_rl_realloc(dst, dsize, dsize + ssize - left - right); + if (IS_ERR(dst)) + return dst; + /* + * We are guaranteed to succeed from here so can start modifying the + * original runlists. + */ + if (right) + __ntfs_rl_merge(src + ssize - 1, dst + loc + 1); + if (left) + __ntfs_rl_merge(dst + loc - 1, src); + + /* FIXME: What does this mean? (AIA) */ + magic = loc + ssize - left; + + /* Move the tail of @dst out of the way, then copy in @src. */ + ntfs_rl_mm(dst, magic, loc + right + 1, dsize - loc - right - 1); + ntfs_rl_mc(dst, loc, src, left, ssize - left); + + /* We may have changed the length of the file, so fix the end marker */ + if (dst[magic].lcn == LCN_ENOENT) + dst[magic].vcn = dst[magic - 1].vcn + dst[magic - 1].length; + return dst; +} + +/** + * ntfs_rl_split - insert a runlist into the centre of a hole + * @dst: original runlist to be worked on + * @dsize: number of elements in @dst (including end marker) + * @src: new runlist to be inserted + * @ssize: number of elements in @src (excluding end marker) + * @loc: index in runlist @dst at which to split and insert @src + * + * Split the runlist @dst at @loc into two and insert @new in between the two + * fragments. No merging of runlists is necessary. Adjust the size of the + * holes either side. + * + * It is up to the caller to serialize access to the runlists @dst and @src. + * + * On success, return a pointer to the new, combined, runlist. Note, both + * runlists @dst and @src are deallocated before returning so you cannot use + * the pointers for anything any more. (Strictly speaking the returned runlist + * may be the same as @dst but this is irrelevant.) + * + * On error, return -errno. Both runlists are left unmodified. The following + * error codes are defined: + * -ENOMEM - Not enough memory to allocate runlist array. + * -EINVAL - Invalid parameters were passed in. + */ +static inline runlist_element *ntfs_rl_split(runlist_element *dst, int dsize, + runlist_element *src, int ssize, int loc) +{ + BUG_ON(!dst); + BUG_ON(!src); + + /* Space required: @dst size + @src size + one new hole. */ + dst = ntfs_rl_realloc(dst, dsize, dsize + ssize + 1); + if (IS_ERR(dst)) + return dst; + /* + * We are guaranteed to succeed from here so can start modifying the + * original runlists. + */ + + /* Move the tail of @dst out of the way, then copy in @src. */ + ntfs_rl_mm(dst, loc + 1 + ssize, loc, dsize - loc); + ntfs_rl_mc(dst, loc + 1, src, 0, ssize); + + /* Adjust the size of the holes either size of @src. */ + dst[loc].length = dst[loc+1].vcn - dst[loc].vcn; + dst[loc+ssize+1].vcn = dst[loc+ssize].vcn + dst[loc+ssize].length; + dst[loc+ssize+1].length = dst[loc+ssize+2].vcn - dst[loc+ssize+1].vcn; + + return dst; +} + +/** + * ntfs_merge_runlists - merge two runlists into one + * @drl: original runlist to be worked on + * @srl: new runlist to be merged into @drl + * + * First we sanity check the two runlists @srl and @drl to make sure that they + * are sensible and can be merged. The runlist @srl must be either after the + * runlist @drl or completely within a hole (or unmapped region) in @drl. + * + * It is up to the caller to serialize access to the runlists @drl and @srl. + * + * Merging of runlists is necessary in two cases: + * 1. When attribute lists are used and a further extent is being mapped. + * 2. When new clusters are allocated to fill a hole or extend a file. + * + * There are four possible ways @srl can be merged. It can: + * - be inserted at the beginning of a hole, + * - split the hole in two and be inserted between the two fragments, + * - be appended at the end of a hole, or it can + * - replace the whole hole. + * It can also be appended to the end of the runlist, which is just a variant + * of the insert case. + * + * On success, return a pointer to the new, combined, runlist. Note, both + * runlists @drl and @srl are deallocated before returning so you cannot use + * the pointers for anything any more. (Strictly speaking the returned runlist + * may be the same as @dst but this is irrelevant.) + * + * On error, return -errno. Both runlists are left unmodified. The following + * error codes are defined: + * -ENOMEM - Not enough memory to allocate runlist array. + * -EINVAL - Invalid parameters were passed in. + * -ERANGE - The runlists overlap and cannot be merged. + */ +runlist_element *ntfs_merge_runlists(runlist_element *drl, + runlist_element *srl) +{ + int di, si; /* Current index into @[ds]rl. */ + int sstart; /* First index with lcn > LCN_RL_NOT_MAPPED. */ + int dins; /* Index into @drl at which to insert @srl. */ + int dend, send; /* Last index into @[ds]rl. */ + int dfinal, sfinal; /* The last index into @[ds]rl with + lcn >= LCN_HOLE. */ + int marker = 0; + VCN marker_vcn = 0; + +#ifdef DEBUG + ntfs_debug("dst:"); + ntfs_debug_dump_runlist(drl); + ntfs_debug("src:"); + ntfs_debug_dump_runlist(srl); +#endif + + /* Check for silly calling... */ + if (unlikely(!srl)) + return drl; + if (IS_ERR(srl) || IS_ERR(drl)) + return ERR_PTR(-EINVAL); + + /* Check for the case where the first mapping is being done now. */ + if (unlikely(!drl)) { + drl = srl; + /* Complete the source runlist if necessary. */ + if (unlikely(drl[0].vcn)) { + /* Scan to the end of the source runlist. */ + for (dend = 0; likely(drl[dend].length); dend++) + ; + drl = ntfs_rl_realloc(drl, dend, dend + 1); + if (IS_ERR(drl)) + return drl; + /* Insert start element at the front of the runlist. */ + ntfs_rl_mm(drl, 1, 0, dend); + drl[0].vcn = 0; + drl[0].lcn = LCN_RL_NOT_MAPPED; + drl[0].length = drl[1].vcn; + } + goto finished; + } + + si = di = 0; + + /* Skip any unmapped start element(s) in the source runlist. */ + while (srl[si].length && srl[si].lcn < (LCN)LCN_HOLE) + si++; + + /* Can't have an entirely unmapped source runlist. */ + BUG_ON(!srl[si].length); + + /* Record the starting points. */ + sstart = si; + + /* + * Skip forward in @drl until we reach the position where @srl needs to + * be inserted. If we reach the end of @drl, @srl just needs to be + * appended to @drl. + */ + for (; drl[di].length; di++) { + if (drl[di].vcn + drl[di].length > srl[sstart].vcn) + break; + } + dins = di; + + /* Sanity check for illegal overlaps. */ + if ((drl[di].vcn == srl[si].vcn) && (drl[di].lcn >= 0) && + (srl[si].lcn >= 0)) { + ntfs_error(NULL, "Run lists overlap. Cannot merge!"); + return ERR_PTR(-ERANGE); + } + + /* Scan to the end of both runlists in order to know their sizes. */ + for (send = si; srl[send].length; send++) + ; + for (dend = di; drl[dend].length; dend++) + ; + + if (srl[send].lcn == (LCN)LCN_ENOENT) + marker_vcn = srl[marker = send].vcn; + + /* Scan to the last element with lcn >= LCN_HOLE. */ + for (sfinal = send; sfinal >= 0 && srl[sfinal].lcn < LCN_HOLE; sfinal--) + ; + for (dfinal = dend; dfinal >= 0 && drl[dfinal].lcn < LCN_HOLE; dfinal--) + ; + + { + BOOL start; + BOOL finish; + int ds = dend + 1; /* Number of elements in drl & srl */ + int ss = sfinal - sstart + 1; + + start = ((drl[dins].lcn < LCN_RL_NOT_MAPPED) || /* End of file */ + (drl[dins].vcn == srl[sstart].vcn)); /* Start of hole */ + finish = ((drl[dins].lcn >= LCN_RL_NOT_MAPPED) && /* End of file */ + ((drl[dins].vcn + drl[dins].length) <= /* End of hole */ + (srl[send - 1].vcn + srl[send - 1].length))); + + /* Or we'll lose an end marker */ + if (start && finish && (drl[dins].length == 0)) + ss++; + if (marker && (drl[dins].vcn + drl[dins].length > srl[send - 1].vcn)) + finish = FALSE; +#if 0 + ntfs_debug("dfinal = %i, dend = %i", dfinal, dend); + ntfs_debug("sstart = %i, sfinal = %i, send = %i", sstart, sfinal, send); + ntfs_debug("start = %i, finish = %i", start, finish); + ntfs_debug("ds = %i, ss = %i, dins = %i", ds, ss, dins); +#endif + if (start) { + if (finish) + drl = ntfs_rl_replace(drl, ds, srl + sstart, ss, dins); + else + drl = ntfs_rl_insert(drl, ds, srl + sstart, ss, dins); + } else { + if (finish) + drl = ntfs_rl_append(drl, ds, srl + sstart, ss, dins); + else + drl = ntfs_rl_split(drl, ds, srl + sstart, ss, dins); + } + if (IS_ERR(drl)) { + ntfs_error(NULL, "Merge failed."); + return drl; + } + ntfs_free(srl); + if (marker) { + ntfs_debug("Triggering marker code."); + for (ds = dend; drl[ds].length; ds++) + ; + /* We only need to care if @srl ended after @drl. */ + if (drl[ds].vcn <= marker_vcn) { + int slots = 0; + + if (drl[ds].vcn == marker_vcn) { + ntfs_debug("Old marker = 0x%llx, replacing " + "with LCN_ENOENT.", + (unsigned long long) + drl[ds].lcn); + drl[ds].lcn = (LCN)LCN_ENOENT; + goto finished; + } + /* + * We need to create an unmapped runlist element in + * @drl or extend an existing one before adding the + * ENOENT terminator. + */ + if (drl[ds].lcn == (LCN)LCN_ENOENT) { + ds--; + slots = 1; + } + if (drl[ds].lcn != (LCN)LCN_RL_NOT_MAPPED) { + /* Add an unmapped runlist element. */ + if (!slots) { + /* FIXME/TODO: We need to have the + * extra memory already! (AIA) */ + drl = ntfs_rl_realloc(drl, ds, ds + 2); + if (!drl) + goto critical_error; + slots = 2; + } + ds++; + /* Need to set vcn if it isn't set already. */ + if (slots != 1) + drl[ds].vcn = drl[ds - 1].vcn + + drl[ds - 1].length; + drl[ds].lcn = (LCN)LCN_RL_NOT_MAPPED; + /* We now used up a slot. */ + slots--; + } + drl[ds].length = marker_vcn - drl[ds].vcn; + /* Finally add the ENOENT terminator. */ + ds++; + if (!slots) { + /* FIXME/TODO: We need to have the extra + * memory already! (AIA) */ + drl = ntfs_rl_realloc(drl, ds, ds + 1); + if (!drl) + goto critical_error; + } + drl[ds].vcn = marker_vcn; + drl[ds].lcn = (LCN)LCN_ENOENT; + drl[ds].length = (s64)0; + } + } + } + +finished: + /* The merge was completed successfully. */ + ntfs_debug("Merged runlist:"); + ntfs_debug_dump_runlist(drl); + return drl; + +critical_error: + /* Critical error! We cannot afford to fail here. */ + ntfs_error(NULL, "Critical error! Not enough memory."); + panic("NTFS: Cannot continue."); +} + +/** + * decompress_mapping_pairs - convert mapping pairs array to runlist + * @vol: ntfs volume on which the attribute resides + * @attr: attribute record whose mapping pairs array to decompress + * @old_rl: optional runlist in which to insert @attr's runlist + * + * It is up to the caller to serialize access to the runlist @old_rl. + * + * Decompress the attribute @attr's mapping pairs array into a runlist. On + * success, return the decompressed runlist. + * + * If @old_rl is not NULL, decompressed runlist is inserted into the + * appropriate place in @old_rl and the resultant, combined runlist is + * returned. The original @old_rl is deallocated. + * + * On error, return -errno. @old_rl is left unmodified in that case. + * + * The following error codes are defined: + * -ENOMEM - Not enough memory to allocate runlist array. + * -EIO - Corrupt runlist. + * -EINVAL - Invalid parameters were passed in. + * -ERANGE - The two runlists overlap. + * + * FIXME: For now we take the conceptionally simplest approach of creating the + * new runlist disregarding the already existing one and then splicing the + * two into one, if that is possible (we check for overlap and discard the new + * runlist if overlap present before returning ERR_PTR(-ERANGE)). + */ +runlist_element *decompress_mapping_pairs(const ntfs_volume *vol, + const ATTR_RECORD *attr, runlist_element *old_rl) +{ + VCN vcn; /* Current vcn. */ + LCN lcn; /* Current lcn. */ + s64 deltaxcn; /* Change in [vl]cn. */ + runlist_element *rl; /* The output runlist. */ + u8 *buf; /* Current position in mapping pairs array. */ + u8 *attr_end; /* End of attribute. */ + int rlsize; /* Size of runlist buffer. */ + u16 rlpos; /* Current runlist position in units of + runlist_elements. */ + u8 b; /* Current byte offset in buf. */ + +#ifdef DEBUG + /* Make sure attr exists and is non-resident. */ + if (!attr || !attr->non_resident || sle64_to_cpu( + attr->data.non_resident.lowest_vcn) < (VCN)0) { + ntfs_error(vol->sb, "Invalid arguments."); + return ERR_PTR(-EINVAL); + } +#endif + /* Start at vcn = lowest_vcn and lcn 0. */ + vcn = sle64_to_cpu(attr->data.non_resident.lowest_vcn); + lcn = 0; + /* Get start of the mapping pairs array. */ + buf = (u8*)attr + le16_to_cpu( + attr->data.non_resident.mapping_pairs_offset); + attr_end = (u8*)attr + le32_to_cpu(attr->length); + if (unlikely(buf < (u8*)attr || buf > attr_end)) { + ntfs_error(vol->sb, "Corrupt attribute."); + return ERR_PTR(-EIO); + } + /* Current position in runlist array. */ + rlpos = 0; + /* Allocate first page and set current runlist size to one page. */ + rl = ntfs_malloc_nofs(rlsize = PAGE_SIZE); + if (unlikely(!rl)) + return ERR_PTR(-ENOMEM); + /* Insert unmapped starting element if necessary. */ + if (vcn) { + rl->vcn = (VCN)0; + rl->lcn = (LCN)LCN_RL_NOT_MAPPED; + rl->length = vcn; + rlpos++; + } + while (buf < attr_end && *buf) { + /* + * Allocate more memory if needed, including space for the + * not-mapped and terminator elements. ntfs_malloc_nofs() + * operates on whole pages only. + */ + if (((rlpos + 3) * sizeof(*old_rl)) > rlsize) { + runlist_element *rl2; + + rl2 = ntfs_malloc_nofs(rlsize + (int)PAGE_SIZE); + if (unlikely(!rl2)) { + ntfs_free(rl); + return ERR_PTR(-ENOMEM); + } + memcpy(rl2, rl, rlsize); + ntfs_free(rl); + rl = rl2; + rlsize += PAGE_SIZE; + } + /* Enter the current vcn into the current runlist element. */ + rl[rlpos].vcn = vcn; + /* + * Get the change in vcn, i.e. the run length in clusters. + * Doing it this way ensures that we signextend negative values. + * A negative run length doesn't make any sense, but hey, I + * didn't make up the NTFS specs and Windows NT4 treats the run + * length as a signed value so that's how it is... + */ + b = *buf & 0xf; + if (b) { + if (unlikely(buf + b > attr_end)) + goto io_error; + for (deltaxcn = (s8)buf[b--]; b; b--) + deltaxcn = (deltaxcn << 8) + buf[b]; + } else { /* The length entry is compulsory. */ + ntfs_error(vol->sb, "Missing length entry in mapping " + "pairs array."); + deltaxcn = (s64)-1; + } + /* + * Assume a negative length to indicate data corruption and + * hence clean-up and return NULL. + */ + if (unlikely(deltaxcn < 0)) { + ntfs_error(vol->sb, "Invalid length in mapping pairs " + "array."); + goto err_out; + } + /* + * Enter the current run length into the current runlist + * element. + */ + rl[rlpos].length = deltaxcn; + /* Increment the current vcn by the current run length. */ + vcn += deltaxcn; + /* + * There might be no lcn change at all, as is the case for + * sparse clusters on NTFS 3.0+, in which case we set the lcn + * to LCN_HOLE. + */ + if (!(*buf & 0xf0)) + rl[rlpos].lcn = (LCN)LCN_HOLE; + else { + /* Get the lcn change which really can be negative. */ + u8 b2 = *buf & 0xf; + b = b2 + ((*buf >> 4) & 0xf); + if (buf + b > attr_end) + goto io_error; + for (deltaxcn = (s8)buf[b--]; b > b2; b--) + deltaxcn = (deltaxcn << 8) + buf[b]; + /* Change the current lcn to its new value. */ + lcn += deltaxcn; +#ifdef DEBUG + /* + * On NTFS 1.2-, apparently can have lcn == -1 to + * indicate a hole. But we haven't verified ourselves + * whether it is really the lcn or the deltaxcn that is + * -1. So if either is found give us a message so we + * can investigate it further! + */ + if (vol->major_ver < 3) { + if (unlikely(deltaxcn == (LCN)-1)) + ntfs_error(vol->sb, "lcn delta == -1"); + if (unlikely(lcn == (LCN)-1)) + ntfs_error(vol->sb, "lcn == -1"); + } +#endif + /* Check lcn is not below -1. */ + if (unlikely(lcn < (LCN)-1)) { + ntfs_error(vol->sb, "Invalid LCN < -1 in " + "mapping pairs array."); + goto err_out; + } + /* Enter the current lcn into the runlist element. */ + rl[rlpos].lcn = lcn; + } + /* Get to the next runlist element. */ + rlpos++; + /* Increment the buffer position to the next mapping pair. */ + buf += (*buf & 0xf) + ((*buf >> 4) & 0xf) + 1; + } + if (unlikely(buf >= attr_end)) + goto io_error; + /* + * If there is a highest_vcn specified, it must be equal to the final + * vcn in the runlist - 1, or something has gone badly wrong. + */ + deltaxcn = sle64_to_cpu(attr->data.non_resident.highest_vcn); + if (unlikely(deltaxcn && vcn - 1 != deltaxcn)) { +mpa_err: + ntfs_error(vol->sb, "Corrupt mapping pairs array in " + "non-resident attribute."); + goto err_out; + } + /* Setup not mapped runlist element if this is the base extent. */ + if (!attr->data.non_resident.lowest_vcn) { + VCN max_cluster; + + max_cluster = (sle64_to_cpu( + attr->data.non_resident.allocated_size) + + vol->cluster_size - 1) >> + vol->cluster_size_bits; + /* + * If there is a difference between the highest_vcn and the + * highest cluster, the runlist is either corrupt or, more + * likely, there are more extents following this one. + */ + if (deltaxcn < --max_cluster) { + ntfs_debug("More extents to follow; deltaxcn = 0x%llx, " + "max_cluster = 0x%llx", + (unsigned long long)deltaxcn, + (unsigned long long)max_cluster); + rl[rlpos].vcn = vcn; + vcn += rl[rlpos].length = max_cluster - deltaxcn; + rl[rlpos].lcn = (LCN)LCN_RL_NOT_MAPPED; + rlpos++; + } else if (unlikely(deltaxcn > max_cluster)) { + ntfs_error(vol->sb, "Corrupt attribute. deltaxcn = " + "0x%llx, max_cluster = 0x%llx", + (unsigned long long)deltaxcn, + (unsigned long long)max_cluster); + goto mpa_err; + } + rl[rlpos].lcn = (LCN)LCN_ENOENT; + } else /* Not the base extent. There may be more extents to follow. */ + rl[rlpos].lcn = (LCN)LCN_RL_NOT_MAPPED; + + /* Setup terminating runlist element. */ + rl[rlpos].vcn = vcn; + rl[rlpos].length = (s64)0; + /* If no existing runlist was specified, we are done. */ + if (!old_rl) { + ntfs_debug("Mapping pairs array successfully decompressed:"); + ntfs_debug_dump_runlist(rl); + return rl; + } + /* Now combine the new and old runlists checking for overlaps. */ + old_rl = ntfs_merge_runlists(old_rl, rl); + if (likely(!IS_ERR(old_rl))) + return old_rl; + ntfs_free(rl); + ntfs_error(vol->sb, "Failed to merge runlists."); + return old_rl; +io_error: + ntfs_error(vol->sb, "Corrupt attribute."); +err_out: + ntfs_free(rl); + return ERR_PTR(-EIO); +} + +/** + * ntfs_vcn_to_lcn - convert a vcn into a lcn given a runlist + * @rl: runlist to use for conversion + * @vcn: vcn to convert + * + * Convert the virtual cluster number @vcn of an attribute into a logical + * cluster number (lcn) of a device using the runlist @rl to map vcns to their + * corresponding lcns. + * + * It is up to the caller to serialize access to the runlist @rl. + * + * Since lcns must be >= 0, we use negative return values with special meaning: + * + * Return value Meaning / Description + * ================================================== + * -1 = LCN_HOLE Hole / not allocated on disk. + * -2 = LCN_RL_NOT_MAPPED This is part of the runlist which has not been + * inserted into the runlist yet. + * -3 = LCN_ENOENT There is no such vcn in the attribute. + * + * Locking: - The caller must have locked the runlist (for reading or writing). + * - This function does not touch the lock. + */ +LCN ntfs_vcn_to_lcn(const runlist_element *rl, const VCN vcn) +{ + int i; + + BUG_ON(vcn < 0); + /* + * If rl is NULL, assume that we have found an unmapped runlist. The + * caller can then attempt to map it and fail appropriately if + * necessary. + */ + if (unlikely(!rl)) + return (LCN)LCN_RL_NOT_MAPPED; + + /* Catch out of lower bounds vcn. */ + if (unlikely(vcn < rl[0].vcn)) + return (LCN)LCN_ENOENT; + + for (i = 0; likely(rl[i].length); i++) { + if (unlikely(vcn < rl[i+1].vcn)) { + if (likely(rl[i].lcn >= (LCN)0)) + return rl[i].lcn + (vcn - rl[i].vcn); + return rl[i].lcn; + } + } + /* + * The terminator element is setup to the correct value, i.e. one of + * LCN_HOLE, LCN_RL_NOT_MAPPED, or LCN_ENOENT. + */ + if (likely(rl[i].lcn < (LCN)0)) + return rl[i].lcn; + /* Just in case... We could replace this with BUG() some day. */ + return (LCN)LCN_ENOENT; +} diff --git a/fs/ntfs/runlist.h b/fs/ntfs/runlist.h new file mode 100644 index 000000000000..8cfb40afb8bf --- /dev/null +++ b/fs/ntfs/runlist.h @@ -0,0 +1,48 @@ +/* + * runlist.h - Defines for runlist handling in NTFS Linux kernel driver. + * Part of the Linux-NTFS project. + * + * Copyright (c) 2001-2004 Anton Altaparmakov + * Copyright (c) 2002 Richard Russon + * + * This program/include file is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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. + * + * You should have received a copy of the GNU General Public License + * along with this program (in the main directory of the Linux-NTFS + * distribution in the file COPYING); if not, write to the Free Software + * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _LINUX_NTFS_RUNLIST_H +#define _LINUX_NTFS_RUNLIST_H + +#include "types.h" +#include "layout.h" +#include "volume.h" + +static inline void init_runlist(runlist *rl) +{ + rl->rl = NULL; + init_rwsem(&rl->lock); +} + +typedef enum { + LCN_HOLE = -1, /* Keep this as highest value or die! */ + LCN_RL_NOT_MAPPED = -2, + LCN_ENOENT = -3, +} LCN_SPECIAL_VALUES; + +extern runlist_element *decompress_mapping_pairs(const ntfs_volume *vol, + const ATTR_RECORD *attr, runlist_element *old_rl); + +extern LCN ntfs_vcn_to_lcn(const runlist_element *rl, const VCN vcn); + +#endif /* _LINUX_NTFS_RUNLIST_H */ -- cgit v1.2.3 From b217296f1171c157faafa37d7be8554c34a21f79 Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Thu, 30 Sep 2004 11:34:56 +0100 Subject: NTFS: Add vol->mft_data_pos and initialize it at mount time. Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 1 + fs/ntfs/super.c | 17 +++++++++++------ fs/ntfs/volume.h | 2 ++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 2f2d62fc2036..05ca11e10afc 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -26,6 +26,7 @@ ToDo/Notes: - Implement extent mft record deallocation fs/ntfs/mft.c::ntfs_extent_mft_record_free(). - Splitt runlist related functions off from attrib.[hc] to runlist.[hc]. + - Add vol->mft_data_pos and initialize it at mount time. 2.1.19 - Many cleanups, improvements, and a minor bug fix. diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c index 2c93c59186c7..7d2a7b668f81 100644 --- a/fs/ntfs/super.c +++ b/fs/ntfs/super.c @@ -827,12 +827,12 @@ static BOOL parse_ntfs_boot_sector(ntfs_volume *vol, const NTFS_BOOT_SECTOR *b) } /** - * setup_lcn_allocator - initialize the cluster allocator - * @vol: volume structure for which to setup the lcn allocator + * ntfs_setup_allocators - initialize the cluster and mft allocators + * @vol: volume structure for which to setup the allocators * - * Setup the cluster (lcn) allocator to the starting values. + * Setup the cluster (lcn) and mft allocators to the starting values. */ -static void setup_lcn_allocator(ntfs_volume *vol) +static void ntfs_setup_allocators(ntfs_volume *vol) { #ifdef NTFS_RW LCN mft_zone_size, mft_lcn; @@ -902,6 +902,11 @@ static void setup_lcn_allocator(ntfs_volume *vol) vol->data2_zone_pos = 0; ntfs_debug("vol->data2_zone_pos = 0x%llx", (unsigned long long)vol->data2_zone_pos); + + /* Set the mft data allocation position to mft record 24. */ + vol->mft_data_pos = 24; + ntfs_debug("vol->mft_data_pos = 0x%llx", + (unsigned long long)vol->mft_data_pos); #endif /* NTFS_RW */ } @@ -2334,8 +2339,8 @@ static int ntfs_fill_super(struct super_block *sb, void *opt, const int silent) */ result = parse_ntfs_boot_sector(vol, (NTFS_BOOT_SECTOR*)bh->b_data); - /* Initialize the cluster allocator. */ - setup_lcn_allocator(vol); + /* Initialize the cluster and mft allocators. */ + ntfs_setup_allocators(vol); brelse(bh); diff --git a/fs/ntfs/volume.h b/fs/ntfs/volume.h index 9775fa65895e..4d0541350fff 100644 --- a/fs/ntfs/volume.h +++ b/fs/ntfs/volume.h @@ -81,6 +81,8 @@ typedef struct { #ifdef NTFS_RW /* Variables used by the cluster and mft allocators. */ + s64 mft_data_pos; /* Mft record number at which to + allocate the next mft record. */ LCN mft_zone_start; /* First cluster of the mft zone. */ LCN mft_zone_end; /* First cluster beyond the mft zone. */ LCN mft_zone_pos; /* Current position in the mft zone. */ -- cgit v1.2.3 From ddd2ef700da998b84dd3013af7d2cff4f9cddb44 Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Thu, 30 Sep 2004 11:49:53 +0100 Subject: NTFS: Rename init_runlist() to ntfs_init_runlist(), ntfs_vcn_to_lcn() to ntfs_rl_vcn_to_lcn(), decompress_mapping_pairs() to ntfs_mapping_pairs_decompress() and adapt all callers. Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 3 +++ fs/ntfs/aops.c | 18 +++++++++--------- fs/ntfs/attrib.c | 12 ++++++------ fs/ntfs/compress.c | 4 ++-- fs/ntfs/inode.c | 18 +++++++++--------- fs/ntfs/runlist.c | 8 ++++---- fs/ntfs/runlist.h | 6 +++--- 7 files changed, 36 insertions(+), 33 deletions(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 05ca11e10afc..2b0306ece05b 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -27,6 +27,9 @@ ToDo/Notes: fs/ntfs/mft.c::ntfs_extent_mft_record_free(). - Splitt runlist related functions off from attrib.[hc] to runlist.[hc]. - Add vol->mft_data_pos and initialize it at mount time. + - Rename init_runlist() to ntfs_init_runlist(), ntfs_vcn_to_lcn() to + ntfs_rl_vcn_to_lcn(), decompress_mapping_pairs() to + ntfs_mapping_pairs_decompress() and adapt all callers. 2.1.19 - Many cleanups, improvements, and a minor bug fix. diff --git a/fs/ntfs/aops.c b/fs/ntfs/aops.c index edcc9fbf6c09..e343c50d80b1 100644 --- a/fs/ntfs/aops.c +++ b/fs/ntfs/aops.c @@ -232,7 +232,7 @@ lock_retry_remap: /* Seek to element containing target vcn. */ while (rl->length && rl[1].vcn <= vcn) rl++; - lcn = ntfs_vcn_to_lcn(rl, vcn); + lcn = ntfs_rl_vcn_to_lcn(rl, vcn); } else lcn = (LCN)LCN_RL_NOT_MAPPED; /* Successful remap. */ @@ -266,7 +266,7 @@ lock_retry_remap: } /* Hard error, zero out region. */ SetPageError(page); - ntfs_error(vol->sb, "ntfs_vcn_to_lcn(vcn = 0x%llx) " + ntfs_error(vol->sb, "ntfs_rl_vcn_to_lcn(vcn = 0x%llx) " "failed with error code 0x%llx%s.", (unsigned long long)vcn, (unsigned long long)-lcn, @@ -274,9 +274,9 @@ lock_retry_remap: // FIXME: Depending on vol->on_errors, do something. } /* - * Either iblock was outside lblock limits or ntfs_vcn_to_lcn() - * returned error. Just zero that portion of the page and set - * the buffer uptodate. + * Either iblock was outside lblock limits or + * ntfs_rl_vcn_to_lcn() returned error. Just zero that portion + * of the page and set the buffer uptodate. */ handle_hole: bh->b_blocknr = -1UL; @@ -637,7 +637,7 @@ lock_retry_remap: /* Seek to element containing target vcn. */ while (rl->length && rl[1].vcn <= vcn) rl++; - lcn = ntfs_vcn_to_lcn(rl, vcn); + lcn = ntfs_rl_vcn_to_lcn(rl, vcn); } else lcn = (LCN)LCN_RL_NOT_MAPPED; /* Successful remap. */ @@ -673,7 +673,7 @@ lock_retry_remap: } /* Failed to map the buffer, even after retrying. */ bh->b_blocknr = -1UL; - ntfs_error(vol->sb, "ntfs_vcn_to_lcn(vcn = 0x%llx) failed " + ntfs_error(vol->sb, "ntfs_rl_vcn_to_lcn(vcn = 0x%llx) failed " "with error code 0x%llx%s.", (unsigned long long)vcn, (unsigned long long)-lcn, @@ -1402,7 +1402,7 @@ lock_retry_remap: /* Seek to element containing target vcn. */ while (rl->length && rl[1].vcn <= vcn) rl++; - lcn = ntfs_vcn_to_lcn(rl, vcn); + lcn = ntfs_rl_vcn_to_lcn(rl, vcn); } else lcn = (LCN)LCN_RL_NOT_MAPPED; if (unlikely(lcn < 0)) { @@ -1451,7 +1451,7 @@ lock_retry_remap: * retrying. */ bh->b_blocknr = -1UL; - ntfs_error(vol->sb, "ntfs_vcn_to_lcn(vcn = " + ntfs_error(vol->sb, "ntfs_rl_vcn_to_lcn(vcn = " "0x%llx) failed with error " "code 0x%llx%s.", (unsigned long long)vcn, diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 6c80b2d1effa..286428e44be6 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -22,7 +22,6 @@ #include #include "ntfs.h" -//#include "dir.h" /** * ntfs_map_runlist - map (a part of) a runlist of an ntfs inode @@ -66,10 +65,11 @@ int ntfs_map_runlist(ntfs_inode *ni, VCN vcn) down_write(&ni->runlist.lock); /* Make sure someone else didn't do the work while we were sleeping. */ - if (likely(ntfs_vcn_to_lcn(ni->runlist.rl, vcn) <= LCN_RL_NOT_MAPPED)) { + if (likely(ntfs_rl_vcn_to_lcn(ni->runlist.rl, vcn) <= + LCN_RL_NOT_MAPPED)) { runlist_element *rl; - rl = decompress_mapping_pairs(ni->vol, ctx->attr, + rl = ntfs_mapping_pairs_decompress(ni->vol, ctx->attr, ni->runlist.rl); if (IS_ERR(rl)) err = PTR_ERR(rl); @@ -398,14 +398,14 @@ int load_attribute_list(ntfs_volume *vol, runlist *runlist, u8 *al_start, rl = runlist->rl; /* Read all clusters specified by the runlist one run at a time. */ while (rl->length) { - lcn = ntfs_vcn_to_lcn(rl, rl->vcn); + lcn = ntfs_rl_vcn_to_lcn(rl, rl->vcn); ntfs_debug("Reading vcn = 0x%llx, lcn = 0x%llx.", (unsigned long long)rl->vcn, (unsigned long long)lcn); /* The attribute list cannot be sparse. */ if (lcn < 0) { - ntfs_error(sb, "ntfs_vcn_to_lcn() failed. Cannot read " - "attribute list."); + ntfs_error(sb, "ntfs_rl_vcn_to_lcn() failed. Cannot " + "read attribute list."); goto err_out; } block = lcn << vol->cluster_size_bits >> block_size_bits; diff --git a/fs/ntfs/compress.c b/fs/ntfs/compress.c index cc3aac147ba3..1adb24dcfcde 100644 --- a/fs/ntfs/compress.c +++ b/fs/ntfs/compress.c @@ -600,7 +600,7 @@ lock_retry_remap: /* Seek to element containing target vcn. */ while (rl->length && rl[1].vcn <= vcn) rl++; - lcn = ntfs_vcn_to_lcn(rl, vcn); + lcn = ntfs_rl_vcn_to_lcn(rl, vcn); } else lcn = (LCN)LCN_RL_NOT_MAPPED; ntfs_debug("Reading vcn = 0x%llx, lcn = 0x%llx.", @@ -926,7 +926,7 @@ map_rl_err: rl_err: up_read(&ni->runlist.lock); - ntfs_error(vol->sb, "ntfs_vcn_to_lcn() failed. Cannot read " + ntfs_error(vol->sb, "ntfs_rl_vcn_to_lcn() failed. Cannot read " "compression block."); goto err_out; diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 873c25d9830f..991b1de122ab 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -376,13 +376,13 @@ static void __ntfs_init_inode(struct super_block *sb, ntfs_inode *ni) ni->seq_no = 0; atomic_set(&ni->count, 1); ni->vol = NTFS_SB(sb); - init_runlist(&ni->runlist); + ntfs_init_runlist(&ni->runlist); init_MUTEX(&ni->mrec_lock); ni->page = NULL; ni->page_ofs = 0; ni->attr_list_size = 0; ni->attr_list = NULL; - init_runlist(&ni->attr_list_rl); + ntfs_init_runlist(&ni->attr_list_rl); ni->itype.index.bmp_ino = NULL; ni->itype.index.block_size = 0; ni->itype.index.vcn_size = 0; @@ -701,7 +701,7 @@ static int ntfs_read_locked_inode(struct inode *vi) * Setup the runlist. No need for locking as we have * exclusive access to the inode at this time. */ - ni->attr_list_rl.rl = decompress_mapping_pairs(vol, + ni->attr_list_rl.rl = ntfs_mapping_pairs_decompress(vol, ctx->attr, NULL); if (IS_ERR(ni->attr_list_rl.rl)) { err = PTR_ERR(ni->attr_list_rl.rl); @@ -1672,7 +1672,7 @@ err_out: * * We solve these problems by starting with the $DATA attribute before anything * else and iterating using ntfs_attr_lookup($DATA) over all extents. As each - * extent is found, we decompress_mapping_pairs() including the implied + * extent is found, we ntfs_mapping_pairs_decompress() including the implied * ntfs_merge_runlists(). Each step of the iteration necessarily provides * sufficient information for the next step to complete. * @@ -1810,7 +1810,7 @@ int ntfs_read_inode_mount(struct inode *vi) goto put_err_out; } /* Setup the runlist. */ - ni->attr_list_rl.rl = decompress_mapping_pairs(vol, + ni->attr_list_rl.rl = ntfs_mapping_pairs_decompress(vol, ctx->attr, NULL); if (IS_ERR(ni->attr_list_rl.rl)) { err = PTR_ERR(ni->attr_list_rl.rl); @@ -1942,11 +1942,11 @@ int ntfs_read_inode_mount(struct inode *vi) * as we have exclusive access to the inode at this time and we * are a mount in progress task, too. */ - nrl = decompress_mapping_pairs(vol, attr, ni->runlist.rl); + nrl = ntfs_mapping_pairs_decompress(vol, attr, ni->runlist.rl); if (IS_ERR(nrl)) { - ntfs_error(sb, "decompress_mapping_pairs() failed with " - "error code %ld. $MFT is corrupt.", - PTR_ERR(nrl)); + ntfs_error(sb, "ntfs_mapping_pairs_decompress() " + "failed with error code %ld. $MFT is " + "corrupt.", PTR_ERR(nrl)); goto put_err_out; } ni->runlist.rl = nrl; diff --git a/fs/ntfs/runlist.c b/fs/ntfs/runlist.c index f28d054691a9..523b5c0261d1 100644 --- a/fs/ntfs/runlist.c +++ b/fs/ntfs/runlist.c @@ -685,7 +685,7 @@ critical_error: } /** - * decompress_mapping_pairs - convert mapping pairs array to runlist + * ntfs_mapping_pairs_decompress - convert mapping pairs array to runlist * @vol: ntfs volume on which the attribute resides * @attr: attribute record whose mapping pairs array to decompress * @old_rl: optional runlist in which to insert @attr's runlist @@ -712,7 +712,7 @@ critical_error: * two into one, if that is possible (we check for overlap and discard the new * runlist if overlap present before returning ERR_PTR(-ERANGE)). */ -runlist_element *decompress_mapping_pairs(const ntfs_volume *vol, +runlist_element *ntfs_mapping_pairs_decompress(const ntfs_volume *vol, const ATTR_RECORD *attr, runlist_element *old_rl) { VCN vcn; /* Current vcn. */ @@ -929,7 +929,7 @@ err_out: } /** - * ntfs_vcn_to_lcn - convert a vcn into a lcn given a runlist + * ntfs_rl_vcn_to_lcn - convert a vcn into a lcn given a runlist * @rl: runlist to use for conversion * @vcn: vcn to convert * @@ -951,7 +951,7 @@ err_out: * Locking: - The caller must have locked the runlist (for reading or writing). * - This function does not touch the lock. */ -LCN ntfs_vcn_to_lcn(const runlist_element *rl, const VCN vcn) +LCN ntfs_rl_vcn_to_lcn(const runlist_element *rl, const VCN vcn) { int i; diff --git a/fs/ntfs/runlist.h b/fs/ntfs/runlist.h index 8cfb40afb8bf..0a01315a4f13 100644 --- a/fs/ntfs/runlist.h +++ b/fs/ntfs/runlist.h @@ -28,7 +28,7 @@ #include "layout.h" #include "volume.h" -static inline void init_runlist(runlist *rl) +static inline void ntfs_init_runlist(runlist *rl) { rl->rl = NULL; init_rwsem(&rl->lock); @@ -40,9 +40,9 @@ typedef enum { LCN_ENOENT = -3, } LCN_SPECIAL_VALUES; -extern runlist_element *decompress_mapping_pairs(const ntfs_volume *vol, +extern runlist_element *ntfs_mapping_pairs_decompress(const ntfs_volume *vol, const ATTR_RECORD *attr, runlist_element *old_rl); -extern LCN ntfs_vcn_to_lcn(const runlist_element *rl, const VCN vcn); +extern LCN ntfs_rl_vcn_to_lcn(const runlist_element *rl, const VCN vcn); #endif /* _LINUX_NTFS_RUNLIST_H */ -- cgit v1.2.3 From 9009565ef4fe7ac83f58ff9938852ecc31691cf5 Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Thu, 30 Sep 2004 12:08:20 +0100 Subject: NTFS: Forgot to lock the mft bitmap when clearing the bit in ntfs_extent_mft_record_free(). Signed-off-by: Anton Altaparmakov --- fs/ntfs/mft.c | 2 ++ fs/ntfs/mft.h | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index ba57d2448eec..4b9be65ca33d 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -1210,7 +1210,9 @@ rollback_error: ntfs_clear_extent_inode(ni); /* Clear the bit in the $MFT/$BITMAP corresponding to this record. */ + down_write(&vol->mftbmp_lock); err = ntfs_bitmap_clear_bit(vol->mftbmp_ino, mft_no); + up_write(&vol->mftbmp_lock); if (unlikely(err)) { /* * The extent inode is gone but we failed to deallocate it in diff --git a/fs/ntfs/mft.h b/fs/ntfs/mft.h index 95f41e8cd377..5f3566779fa8 100644 --- a/fs/ntfs/mft.h +++ b/fs/ntfs/mft.h @@ -27,8 +27,6 @@ #include "inode.h" -extern int format_mft_record(ntfs_inode *ni, MFT_RECORD *m); - extern MFT_RECORD *map_mft_record(ntfs_inode *ni); extern void unmap_mft_record(ntfs_inode *ni); -- cgit v1.2.3 From 5d303083bcba99ebd2c42024d7d4253e42bc1eac Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Thu, 30 Sep 2004 13:14:02 +0100 Subject: NTFS: Add fs/ntfs/runlist.[hc]::ntfs_get_nr_significant_bytes(), ntfs_get_size_for_mapping_pairs(), ntfs_write_significant_bytes(), and ntfs_mapping_pairs_build(), adapted from libntfs. Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 3 + fs/ntfs/runlist.c | 337 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ fs/ntfs/runlist.h | 7 ++ 3 files changed, 347 insertions(+) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 2b0306ece05b..3518677ab23e 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -30,6 +30,9 @@ ToDo/Notes: - Rename init_runlist() to ntfs_init_runlist(), ntfs_vcn_to_lcn() to ntfs_rl_vcn_to_lcn(), decompress_mapping_pairs() to ntfs_mapping_pairs_decompress() and adapt all callers. + - Add fs/ntfs/runlist.[hc]::ntfs_get_nr_significant_bytes(), + ntfs_get_size_for_mapping_pairs(), ntfs_write_significant_bytes(), + and ntfs_mapping_pairs_build(), adapted from libntfs. 2.1.19 - Many cleanups, improvements, and a minor bug fix. diff --git a/fs/ntfs/runlist.c b/fs/ntfs/runlist.c index 523b5c0261d1..ac44caaae866 100644 --- a/fs/ntfs/runlist.c +++ b/fs/ntfs/runlist.c @@ -984,3 +984,340 @@ LCN ntfs_rl_vcn_to_lcn(const runlist_element *rl, const VCN vcn) /* Just in case... We could replace this with BUG() some day. */ return (LCN)LCN_ENOENT; } + +/** + * ntfs_get_nr_significant_bytes - get number of bytes needed to store a number + * @n: number for which to get the number of bytes for + * + * Return the number of bytes required to store @n unambiguously as + * a signed number. + * + * This is used in the context of the mapping pairs array to determine how + * many bytes will be needed in the array to store a given logical cluster + * number (lcn) or a specific run length. + * + * Return the number of bytes written. This function cannot fail. + */ +static inline int ntfs_get_nr_significant_bytes(const s64 n) +{ + s64 l = n; + int i; + s8 j; + + i = 0; + do { + l >>= 8; + i++; + } while (l != 0 && l != -1); + j = (n >> 8 * (i - 1)) & 0xff; + /* If the sign bit is wrong, we need an extra byte. */ + if ((n < 0 && j >= 0) || (n > 0 && j < 0)) + i++; + return i; +} + +/** + * ntfs_get_size_for_mapping_pairs - get bytes needed for mapping pairs array + * @vol: ntfs volume (needed for the ntfs version) + * @rl: locked runlist to determine the size of the mapping pairs of + * @start_vcn: vcn at which to start the mapping pairs array + * + * Walk the locked runlist @rl and calculate the size in bytes of the mapping + * pairs array corresponding to the runlist @rl, starting at vcn @start_vcn. + * This for example allows us to allocate a buffer of the right size when + * building the mapping pairs array. + * + * If @rl is NULL, just return 1 (for the single terminator byte). + * + * Return the calculated size in bytes on success. On error, return -errno. + * The following error codes are defined: + * -EINVAL - Run list contains unmapped elements. Make sure to only pass + * fully mapped runlists to this function. + * -EIO - The runlist is corrupt. + * + * Locking: @rl must be locked on entry (either for reading or writing), it + * remains locked throughout, and is left locked upon return. + */ +int ntfs_get_size_for_mapping_pairs(const ntfs_volume *vol, + const runlist_element *rl, const VCN start_vcn) +{ + LCN prev_lcn; + int rls; + + BUG_ON(start_vcn < 0); + if (!rl) { + BUG_ON(start_vcn); + return 1; + } + /* Skip to runlist element containing @start_vcn. */ + while (rl->length && start_vcn >= rl[1].vcn) + rl++; + if ((!rl->length && start_vcn > rl->vcn) || start_vcn < rl->vcn) + return -EINVAL; + prev_lcn = 0; + /* Always need the termining zero byte. */ + rls = 1; + /* Do the first partial run if present. */ + if (start_vcn > rl->vcn) { + s64 delta; + + /* We know rl->length != 0 already. */ + if (rl->length < 0 || rl->lcn < LCN_HOLE) + goto err_out; + delta = start_vcn - rl->vcn; + /* Header byte + length. */ + rls += 1 + ntfs_get_nr_significant_bytes(rl->length - delta); + /* + * If the logical cluster number (lcn) denotes a hole and we + * are on NTFS 3.0+, we don't store it at all, i.e. we need + * zero space. On earlier NTFS versions we just store the lcn. + * Note: this assumes that on NTFS 1.2-, holes are stored with + * an lcn of -1 and not a delta_lcn of -1 (unless both are -1). + */ + if (rl->lcn >= 0 || vol->major_ver < 3) { + prev_lcn = rl->lcn; + if (rl->lcn >= 0) + prev_lcn += delta; + /* Change in lcn. */ + rls += ntfs_get_nr_significant_bytes(prev_lcn); + } + /* Go to next runlist element. */ + rl++; + } + /* Do the full runs. */ + for (; rl->length; rl++) { + if (rl->length < 0 || rl->lcn < LCN_HOLE) + goto err_out; + /* Header byte + length. */ + rls += 1 + ntfs_get_nr_significant_bytes(rl->length); + /* + * If the logical cluster number (lcn) denotes a hole and we + * are on NTFS 3.0+, we don't store it at all, i.e. we need + * zero space. On earlier NTFS versions we just store the lcn. + * Note: this assumes that on NTFS 1.2-, holes are stored with + * an lcn of -1 and not a delta_lcn of -1 (unless both are -1). + */ + if (rl->lcn >= 0 || vol->major_ver < 3) { + /* Change in lcn. */ + rls += ntfs_get_nr_significant_bytes(rl->lcn - + prev_lcn); + prev_lcn = rl->lcn; + } + } + return rls; +err_out: + if (rl->lcn == LCN_RL_NOT_MAPPED) + rls = -EINVAL; + else + rls = -EIO; + return rls; +} + +/** + * ntfs_write_significant_bytes - write the significant bytes of a number + * @dst: destination buffer to write to + * @dst_max: pointer to last byte of destination buffer for bounds checking + * @n: number whose significant bytes to write + * + * Store in @dst, the minimum bytes of the number @n which are required to + * identify @n unambiguously as a signed number, taking care not to exceed + * @dest_max, the maximum position within @dst to which we are allowed to + * write. + * + * This is used when building the mapping pairs array of a runlist to compress + * a given logical cluster number (lcn) or a specific run length to the minumum + * size possible. + * + * Return the number of bytes written on success. On error, i.e. the + * destination buffer @dst is too small, return -ENOSPC. + */ +static inline int ntfs_write_significant_bytes(s8 *dst, const s8 *dst_max, + const s64 n) +{ + s64 l = n; + int i; + s8 j; + + i = 0; + do { + if (dst > dst_max) + goto err_out; + *dst++ = l & 0xffll; + l >>= 8; + i++; + } while (l != 0 && l != -1); + j = (n >> 8 * (i - 1)) & 0xff; + /* If the sign bit is wrong, we need an extra byte. */ + if (n < 0 && j >= 0) { + if (dst > dst_max) + goto err_out; + i++; + *dst = (s8)-1; + } else if (n > 0 && j < 0) { + if (dst > dst_max) + goto err_out; + i++; + *dst = (s8)0; + } + return i; +err_out: + return -ENOSPC; +} + +/** + * ntfs_mapping_pairs_build - build the mapping pairs array from a runlist + * @vol: ntfs volume (needed for the ntfs version) + * @dst: destination buffer to which to write the mapping pairs array + * @dst_len: size of destination buffer @dst in bytes + * @rl: locked runlist for which to build the mapping pairs array + * @start_vcn: vcn at which to start the mapping pairs array + * @stop_vcn: first vcn outside destination buffer on success or -ENOSPC + * + * Create the mapping pairs array from the locked runlist @rl, starting at vcn + * @start_vcn and save the array in @dst. @dst_len is the size of @dst in + * bytes and it should be at least equal to the value obtained by calling + * ntfs_get_size_for_mapping_pairs(). + * + * If @rl is NULL, just write a single terminator byte to @dst. + * + * On success or -ENOSPC error, if @stop_vcn is not NULL, *@stop_vcn is set to + * the first vcn outside the destination buffer. Note that on error, @dst has + * been filled with all the mapping pairs that will fit, thus it can be treated + * as partial success, in that a new attribute extent needs to be created or + * the next extent has to be used and the mapping pairs build has to be + * continued with @start_vcn set to *@stop_vcn. + * + * Return 0 on success and -errno on error. The following error codes are + * defined: + * -EINVAL - Run list contains unmapped elements. Make sure to only pass + * fully mapped runlists to this function. + * -EIO - The runlist is corrupt. + * -ENOSPC - The destination buffer is too small. + * + * Locking: @rl must be locked on entry (either for reading or writing), it + * remains locked throughout, and is left locked upon return. + */ +int ntfs_mapping_pairs_build(const ntfs_volume *vol, s8 *dst, + const int dst_len, const runlist_element *rl, + const VCN start_vcn, VCN *const stop_vcn) +{ + LCN prev_lcn; + s8 *dst_max, *dst_next; + int err = -ENOSPC; + s8 len_len, lcn_len; + + BUG_ON(start_vcn < 0); + BUG_ON(dst_len < 1); + if (!rl) { + BUG_ON(start_vcn); + if (stop_vcn) + *stop_vcn = 0; + /* Terminator byte. */ + *dst = 0; + return 0; + } + /* Skip to runlist element containing @start_vcn. */ + while (rl->length && start_vcn >= rl[1].vcn) + rl++; + if ((!rl->length && start_vcn > rl->vcn) || start_vcn < rl->vcn) + return -EINVAL; + /* + * @dst_max is used for bounds checking in + * ntfs_write_significant_bytes(). + */ + dst_max = dst + dst_len - 1; + prev_lcn = 0; + /* Do the first partial run if present. */ + if (start_vcn > rl->vcn) { + s64 delta; + + /* We know rl->length != 0 already. */ + if (rl->length < 0 || rl->lcn < LCN_HOLE) + goto err_out; + delta = start_vcn - rl->vcn; + /* Write length. */ + len_len = ntfs_write_significant_bytes(dst + 1, dst_max, + rl->length - delta); + if (len_len < 0) + goto size_err; + /* + * If the logical cluster number (lcn) denotes a hole and we + * are on NTFS 3.0+, we don't store it at all, i.e. we need + * zero space. On earlier NTFS versions we just write the lcn + * change. FIXME: Do we need to write the lcn change or just + * the lcn in that case? Not sure as I have never seen this + * case on NT4. - We assume that we just need to write the lcn + * change until someone tells us otherwise... (AIA) + */ + if (rl->lcn >= 0 || vol->major_ver < 3) { + prev_lcn = rl->lcn; + if (rl->lcn >= 0) + prev_lcn += delta; + /* Write change in lcn. */ + lcn_len = ntfs_write_significant_bytes(dst + 1 + + len_len, dst_max, prev_lcn); + if (lcn_len < 0) + goto size_err; + } else + lcn_len = 0; + dst_next = dst + len_len + lcn_len + 1; + if (dst_next > dst_max) + goto size_err; + /* Update header byte. */ + *dst = lcn_len << 4 | len_len; + /* Position at next mapping pairs array element. */ + dst = dst_next; + /* Go to next runlist element. */ + rl++; + } + /* Do the full runs. */ + for (; rl->length; rl++) { + if (rl->length < 0 || rl->lcn < LCN_HOLE) + goto err_out; + /* Write length. */ + len_len = ntfs_write_significant_bytes(dst + 1, dst_max, + rl->length); + if (len_len < 0) + goto size_err; + /* + * If the logical cluster number (lcn) denotes a hole and we + * are on NTFS 3.0+, we don't store it at all, i.e. we need + * zero space. On earlier NTFS versions we just write the lcn + * change. FIXME: Do we need to write the lcn change or just + * the lcn in that case? Not sure as I have never seen this + * case on NT4. - We assume that we just need to write the lcn + * change until someone tells us otherwise... (AIA) + */ + if (rl->lcn >= 0 || vol->major_ver < 3) { + /* Write change in lcn. */ + lcn_len = ntfs_write_significant_bytes(dst + 1 + + len_len, dst_max, rl->lcn - prev_lcn); + if (lcn_len < 0) + goto size_err; + prev_lcn = rl->lcn; + } else + lcn_len = 0; + dst_next = dst + len_len + lcn_len + 1; + if (dst_next > dst_max) + goto size_err; + /* Update header byte. */ + *dst = lcn_len << 4 | len_len; + /* Position at next mapping pairs array element. */ + dst = dst_next; + } + /* Success. */ + err = 0; +size_err: + /* Set stop vcn. */ + if (stop_vcn) + *stop_vcn = rl->vcn; + /* Add terminator byte. */ + *dst = 0; + return err; +err_out: + if (rl->lcn == LCN_RL_NOT_MAPPED) + err = -EINVAL; + else + err = -EIO; + return err; +} diff --git a/fs/ntfs/runlist.h b/fs/ntfs/runlist.h index 0a01315a4f13..cc230c8dec8c 100644 --- a/fs/ntfs/runlist.h +++ b/fs/ntfs/runlist.h @@ -45,4 +45,11 @@ extern runlist_element *ntfs_mapping_pairs_decompress(const ntfs_volume *vol, extern LCN ntfs_rl_vcn_to_lcn(const runlist_element *rl, const VCN vcn); +extern int ntfs_get_size_for_mapping_pairs(const ntfs_volume *vol, + const runlist_element *rl, const VCN start_vcn); + +extern int ntfs_mapping_pairs_build(const ntfs_volume *vol, s8 *dst, + const int dst_len, const runlist_element *rl, + const VCN start_vcn, VCN *const stop_vcn); + #endif /* _LINUX_NTFS_RUNLIST_H */ -- cgit v1.2.3 From 16a60b36259fb85def448516a177e884f8a9a941 Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Thu, 30 Sep 2004 17:11:28 +0100 Subject: NTFS: Rename ntfs_merge_runlists() to ntfs_runlists_merge(). Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 3 ++- fs/ntfs/inode.c | 2 +- fs/ntfs/runlist.c | 6 +++--- fs/ntfs/runlist.h | 3 +++ 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 3518677ab23e..52b1dd59f06c 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -29,7 +29,8 @@ ToDo/Notes: - Add vol->mft_data_pos and initialize it at mount time. - Rename init_runlist() to ntfs_init_runlist(), ntfs_vcn_to_lcn() to ntfs_rl_vcn_to_lcn(), decompress_mapping_pairs() to - ntfs_mapping_pairs_decompress() and adapt all callers. + ntfs_mapping_pairs_decompress(), ntfs_merge_runlists() to + ntfs_runlists_merge() and adapt all callers. - Add fs/ntfs/runlist.[hc]::ntfs_get_nr_significant_bytes(), ntfs_get_size_for_mapping_pairs(), ntfs_write_significant_bytes(), and ntfs_mapping_pairs_build(), adapted from libntfs. diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 991b1de122ab..900439db32e6 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -1673,7 +1673,7 @@ err_out: * We solve these problems by starting with the $DATA attribute before anything * else and iterating using ntfs_attr_lookup($DATA) over all extents. As each * extent is found, we ntfs_mapping_pairs_decompress() including the implied - * ntfs_merge_runlists(). Each step of the iteration necessarily provides + * ntfs_runlists_merge(). Each step of the iteration necessarily provides * sufficient information for the next step to complete. * * This should work but there are two possible pit falls (see inline comments diff --git a/fs/ntfs/runlist.c b/fs/ntfs/runlist.c index ac44caaae866..29e28d844cc0 100644 --- a/fs/ntfs/runlist.c +++ b/fs/ntfs/runlist.c @@ -449,7 +449,7 @@ static inline runlist_element *ntfs_rl_split(runlist_element *dst, int dsize, } /** - * ntfs_merge_runlists - merge two runlists into one + * ntfs_runlists_merge - merge two runlists into one * @drl: original runlist to be worked on * @srl: new runlist to be merged into @drl * @@ -482,7 +482,7 @@ static inline runlist_element *ntfs_rl_split(runlist_element *dst, int dsize, * -EINVAL - Invalid parameters were passed in. * -ERANGE - The runlists overlap and cannot be merged. */ -runlist_element *ntfs_merge_runlists(runlist_element *drl, +runlist_element *ntfs_runlists_merge(runlist_element *drl, runlist_element *srl) { int di, si; /* Current index into @[ds]rl. */ @@ -915,7 +915,7 @@ mpa_err: return rl; } /* Now combine the new and old runlists checking for overlaps. */ - old_rl = ntfs_merge_runlists(old_rl, rl); + old_rl = ntfs_runlists_merge(old_rl, rl); if (likely(!IS_ERR(old_rl))) return old_rl; ntfs_free(rl); diff --git a/fs/ntfs/runlist.h b/fs/ntfs/runlist.h index cc230c8dec8c..c3752c7a1409 100644 --- a/fs/ntfs/runlist.h +++ b/fs/ntfs/runlist.h @@ -40,6 +40,9 @@ typedef enum { LCN_ENOENT = -3, } LCN_SPECIAL_VALUES; +extern runlist_element *ntfs_runlists_merge(runlist_element *drl, + runlist_element *srl); + extern runlist_element *ntfs_mapping_pairs_decompress(const ntfs_volume *vol, const ATTR_RECORD *attr, runlist_element *old_rl); -- cgit v1.2.3 From f04332b58d00282029cc13ad9d81b575b52ddd51 Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Thu, 30 Sep 2004 17:20:20 +0100 Subject: NTFS: - Add fs/ntfs/lcnalloc.h::ntfs_cluster_free_from_rl() which is a static inline wrapper for ntfs_cluster_free_from_rl_nolock() which takes the cluster bitmap lock for the duration of the call. - Make fs/ntfs/lcnalloc.c::ntfs_cluster_free_from_rl_nolock() not static and add a declaration for it to lcnalloc.h. Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 5 +++++ fs/ntfs/lcnalloc.c | 2 +- fs/ntfs/lcnalloc.h | 28 ++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 52b1dd59f06c..f6c31a11c20d 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -34,6 +34,11 @@ ToDo/Notes: - Add fs/ntfs/runlist.[hc]::ntfs_get_nr_significant_bytes(), ntfs_get_size_for_mapping_pairs(), ntfs_write_significant_bytes(), and ntfs_mapping_pairs_build(), adapted from libntfs. + - Make fs/ntfs/lcnalloc.c::ntfs_cluster_free_from_rl_nolock() not + static, add a declaration for it to lcnalloc.h. + - Add fs/ntfs/lcnalloc.h::ntfs_cluster_free_from_rl() which is a static + inline wrapper for ntfs_cluster_free_from_rl_nolock() which takes the + cluster bitmap lock for the duration of the call. 2.1.19 - Many cleanups, improvements, and a minor bug fix. diff --git a/fs/ntfs/lcnalloc.c b/fs/ntfs/lcnalloc.c index 748ed0d78d3b..12369fdf566a 100644 --- a/fs/ntfs/lcnalloc.c +++ b/fs/ntfs/lcnalloc.c @@ -46,7 +46,7 @@ * Locking: - The volume lcn bitmap must be locked for writing on entry and is * left locked on return. */ -static int ntfs_cluster_free_from_rl_nolock(ntfs_volume *vol, +int ntfs_cluster_free_from_rl_nolock(ntfs_volume *vol, const runlist_element *rl) { struct inode *lcnbmp_vi = vol->lcnbmp_ino; diff --git a/fs/ntfs/lcnalloc.h b/fs/ntfs/lcnalloc.h index f9292e882adc..b9ebb31100a1 100644 --- a/fs/ntfs/lcnalloc.h +++ b/fs/ntfs/lcnalloc.h @@ -78,6 +78,34 @@ static inline s64 ntfs_cluster_free(struct inode *vi, const VCN start_vcn, return __ntfs_cluster_free(vi, start_vcn, count, FALSE); } +extern int ntfs_cluster_free_from_rl_nolock(ntfs_volume *vol, + const runlist_element *rl); + +/** + * ntfs_cluster_free_from_rl - free clusters from runlist + * @vol: mounted ntfs volume on which to free the clusters + * @rl: runlist describing the clusters to free + * + * Free all the clusters described by the runlist @rl on the volume @vol. In + * the case of an error being returned, at least some of the clusters were not + * freed. + * + * Return 0 on success and -errno on error. + * + * Locking: This function takes the volume lcn bitmap lock for writing and + * modifies the bitmap contents. + */ +static inline int ntfs_cluster_free_from_rl(ntfs_volume *vol, + const runlist_element *rl) +{ + int ret; + + down_write(&vol->lcnbmp_lock); + ret = ntfs_cluster_free_from_rl_nolock(vol, rl); + up_write(&vol->lcnbmp_lock); + return ret; +} + #endif /* NTFS_RW */ #endif /* defined _LINUX_NTFS_LCNALLOC_H */ -- cgit v1.2.3 From a541769c970d14c1199f0a9c3f97132eab4fe9bf Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Thu, 30 Sep 2004 17:37:42 +0100 Subject: NTFS: Add fs/ntfs/attrib.[hc]::ntfs_attr_record_resize(). Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 3 ++- fs/ntfs/attrib.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ fs/ntfs/attrib.h | 2 ++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index f6c31a11c20d..798fb4eb54c2 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -35,10 +35,11 @@ ToDo/Notes: ntfs_get_size_for_mapping_pairs(), ntfs_write_significant_bytes(), and ntfs_mapping_pairs_build(), adapted from libntfs. - Make fs/ntfs/lcnalloc.c::ntfs_cluster_free_from_rl_nolock() not - static, add a declaration for it to lcnalloc.h. + static and add a declaration for it to lcnalloc.h. - Add fs/ntfs/lcnalloc.h::ntfs_cluster_free_from_rl() which is a static inline wrapper for ntfs_cluster_free_from_rl_nolock() which takes the cluster bitmap lock for the duration of the call. + - Add fs/ntfs/attrib.[hc]::ntfs_attr_record_resize(). 2.1.19 - Many cleanups, improvements, and a minor bug fix. diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 286428e44be6..16ad51eff58b 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -942,3 +942,47 @@ void ntfs_attr_put_search_ctx(ntfs_attr_search_ctx *ctx) kmem_cache_free(ntfs_attr_ctx_cache, ctx); return; } + +/** + * ntfs_attr_record_resize - resize an attribute record + * @m: mft record containing attribute record + * @a: attribute record to resize + * @new_size: new size in bytes to which to resize the attribute record @a + * + * Resize the attribute record @a, i.e. the resident part of the attribute, in + * the mft record @m to @new_size bytes. + * + * Return 0 on success and -errno on error. The following error codes are + * defined: + * -ENOSPC - Not enough space in the mft record @m to perform the resize. + * + * Note: On error, no modifications have been performed whatsoever. + * + * Warning: If you make a record smaller without having copied all the data you + * are interested in the data may be overwritten. + */ +int ntfs_attr_record_resize(MFT_RECORD *m, ATTR_RECORD *a, u32 new_size) +{ + ntfs_debug("Entering for new_size %u.", new_size); + /* Align to 8 bytes if it is not already done. */ + if (new_size & 7) + new_size = (new_size + 7) & ~7; + /* If the actual attribute length has changed, move things around. */ + if (new_size != le32_to_cpu(a->length)) { + u32 new_muse = le32_to_cpu(m->bytes_in_use) - + le32_to_cpu(a->length) + new_size; + /* Not enough space in this mft record. */ + if (new_muse > le32_to_cpu(m->bytes_allocated)) + return -ENOSPC; + /* Move attributes following @a to their new location. */ + memmove((u8*)a + new_size, (u8*)a + le32_to_cpu(a->length), + le32_to_cpu(m->bytes_in_use) - ((u8*)a - + (u8*)m) - le32_to_cpu(a->length)); + /* Adjust @m to reflect the change in used space. */ + m->bytes_in_use = cpu_to_le32(new_muse); + /* Adjust @a to reflect the new size. */ + if (new_size >= offsetof(ATTR_REC, length) + sizeof(a->length)) + a->length = cpu_to_le32(new_size); + } + return 0; +} diff --git a/fs/ntfs/attrib.h b/fs/ntfs/attrib.h index 16a5eca832de..a0c97986b09c 100644 --- a/fs/ntfs/attrib.h +++ b/fs/ntfs/attrib.h @@ -84,4 +84,6 @@ extern ntfs_attr_search_ctx *ntfs_attr_get_search_ctx(ntfs_inode *ni, MFT_RECORD *mrec); extern void ntfs_attr_put_search_ctx(ntfs_attr_search_ctx *ctx); +extern int ntfs_attr_record_resize(MFT_RECORD *m, ATTR_RECORD *a, u32 new_size); + #endif /* _LINUX_NTFS_ATTRIB_H */ -- cgit v1.2.3 From 0eb728e200b95b82f6558185e3906963440f092d Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Fri, 1 Oct 2004 13:04:26 +0100 Subject: NTFS: Implement the equivalent of memset() for an ntfs attribute in fs/ntfs/attrib.[hc]::ntfs_attr_set() and switch fs/ntfs/logfile.c::ntfs_empty_logfile() to using it. Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 3 ++ fs/ntfs/attrib.c | 141 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ fs/ntfs/attrib.h | 3 ++ fs/ntfs/logfile.c | 60 ++++------------------- 4 files changed, 157 insertions(+), 50 deletions(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 798fb4eb54c2..1f505f16a316 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -40,6 +40,9 @@ ToDo/Notes: inline wrapper for ntfs_cluster_free_from_rl_nolock() which takes the cluster bitmap lock for the duration of the call. - Add fs/ntfs/attrib.[hc]::ntfs_attr_record_resize(). + - Implement the equivalent of memset() for an ntfs attribute in + fs/ntfs/attrib.[hc]::ntfs_attr_set() and switch + fs/ntfs/logfile.c::ntfs_empty_logfile() to using it. 2.1.19 - Many cleanups, improvements, and a minor bug fix. diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 16ad51eff58b..3058a71dde4f 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -986,3 +986,144 @@ int ntfs_attr_record_resize(MFT_RECORD *m, ATTR_RECORD *a, u32 new_size) } return 0; } + +/** + * ntfs_attr_set - fill (a part of) an attribute with a byte + * @ni: ntfs inode describing the attribute to fill + * @ofs: offset inside the attribute at which to start to fill + * @cnt: number of bytes to fill + * @val: the unsigned 8-bit value with which to fill the attribute + * + * Fill @cnt bytes of the attribute described by the ntfs inode @ni starting at + * byte offset @ofs inside the attribute with the constant byte @val. + * + * This function is effectively like memset() applied to an ntfs attribute. + * + * Return 0 on success and -errno on error. An error code of -ESPIPE means + * that @ofs + @cnt were outside the end of the attribute and no write was + * performed. + */ +int ntfs_attr_set(ntfs_inode *ni, const s64 ofs, const s64 cnt, const u8 val) +{ + ntfs_volume *vol = ni->vol; + struct address_space *mapping; + struct page *page; + u8 *kaddr; + pgoff_t idx, end; + unsigned int start_ofs, end_ofs, size; + + ntfs_debug("Entering for ofs 0x%llx, cnt 0x%llx, val 0x%hx.", + (long long)ofs, (long long)cnt, val); + BUG_ON(ofs < 0); + BUG_ON(cnt < 0); + if (!cnt) + goto done; + mapping = VFS_I(ni)->i_mapping; + /* Work out the starting index and page offset. */ + idx = ofs >> PAGE_CACHE_SHIFT; + start_ofs = ofs & ~PAGE_CACHE_MASK; + /* Work out the ending index and page offset. */ + end = ofs + cnt; + end_ofs = end & ~PAGE_CACHE_MASK; + /* If the end is outside the inode size return -ESPIPE. */ + if (unlikely(end > VFS_I(ni)->i_size)) { + ntfs_error(vol->sb, "Request exceeds end of attribute."); + return -ESPIPE; + } + end >>= PAGE_CACHE_SHIFT; + /* If there is a first partial page, need to do it the slow way. */ + if (start_ofs) { + page = read_cache_page(mapping, idx, + (filler_t*)mapping->a_ops->readpage, NULL); + if (IS_ERR(page)) { + ntfs_error(vol->sb, "Failed to read first partial " + "page (sync error, index 0x%lx).", idx); + return PTR_ERR(page); + } + wait_on_page_locked(page); + if (unlikely(!PageUptodate(page))) { + ntfs_error(vol->sb, "Failed to read first partial page " + "(async error, index 0x%lx).", idx); + page_cache_release(page); + return PTR_ERR(page); + } + /* + * If the last page is the same as the first page, need to + * limit the write to the end offset. + */ + size = PAGE_CACHE_SIZE; + if (idx == end) + size = end_ofs; + kaddr = kmap_atomic(page, KM_USER0); + memset(kaddr + start_ofs, val, size - start_ofs); + flush_dcache_page(page); + kunmap_atomic(kaddr, KM_USER0); + set_page_dirty(page); + page_cache_release(page); + if (idx == end) + goto done; + idx++; + } + /* Do the whole pages the fast way. */ + for (; idx < end; idx++) { + /* Find or create the current page. (The page is locked.) */ + page = grab_cache_page(mapping, idx); + if (unlikely(!page)) { + ntfs_error(vol->sb, "Insufficient memory to grab " + "page (index 0x%lx).", idx); + return -ENOMEM; + } + kaddr = kmap_atomic(page, KM_USER0); + memset(kaddr, val, PAGE_CACHE_SIZE); + flush_dcache_page(page); + kunmap_atomic(kaddr, KM_USER0); + /* + * If the page has buffers, mark them uptodate since buffer + * state and not page state is definitive in 2.6 kernels. + */ + if (page_has_buffers(page)) { + struct buffer_head *bh, *head; + + bh = head = page_buffers(page); + do { + set_buffer_uptodate(bh); + } while ((bh = bh->b_this_page) != head); + } + /* Now that buffers are uptodate, set the page uptodate, too. */ + SetPageUptodate(page); + /* + * Set the page and all its buffers dirty and mark the inode + * dirty, too. The VM will write the page later on. + */ + set_page_dirty(page); + /* Finally unlock and release the page. */ + unlock_page(page); + page_cache_release(page); + } + /* If there is a last partial page, need to do it the slow way. */ + if (end_ofs) { + page = read_cache_page(mapping, idx, + (filler_t*)mapping->a_ops->readpage, NULL); + if (IS_ERR(page)) { + ntfs_error(vol->sb, "Failed to read last partial page " + "(sync error, index 0x%lx).", idx); + return PTR_ERR(page); + } + wait_on_page_locked(page); + if (unlikely(!PageUptodate(page))) { + ntfs_error(vol->sb, "Failed to read last partial page " + "(async error, index 0x%lx).", idx); + page_cache_release(page); + return PTR_ERR(page); + } + kaddr = kmap_atomic(page, KM_USER0); + memset(kaddr, val, end_ofs); + flush_dcache_page(page); + kunmap_atomic(kaddr, KM_USER0); + set_page_dirty(page); + page_cache_release(page); + } +done: + ntfs_debug("Done."); + return 0; +} diff --git a/fs/ntfs/attrib.h b/fs/ntfs/attrib.h index a0c97986b09c..3d98ce8b7aa0 100644 --- a/fs/ntfs/attrib.h +++ b/fs/ntfs/attrib.h @@ -86,4 +86,7 @@ extern void ntfs_attr_put_search_ctx(ntfs_attr_search_ctx *ctx); extern int ntfs_attr_record_resize(MFT_RECORD *m, ATTR_RECORD *a, u32 new_size); +extern int ntfs_attr_set(ntfs_inode *ni, const s64 ofs, const s64 cnt, + const u8 val); + #endif /* _LINUX_NTFS_ATTRIB_H */ diff --git a/fs/ntfs/logfile.c b/fs/ntfs/logfile.c index 1869a4375898..84ed2ef14a32 100644 --- a/fs/ntfs/logfile.c +++ b/fs/ntfs/logfile.c @@ -681,60 +681,20 @@ err_out: BOOL ntfs_empty_logfile(struct inode *log_vi) { ntfs_volume *vol = NTFS_SB(log_vi->i_sb); - struct address_space *mapping; - pgoff_t idx, end; ntfs_debug("Entering."); - if (NVolLogFileEmpty(vol)) - goto done; - mapping = log_vi->i_mapping; - end = (log_vi->i_size + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT; - for (idx = 0; idx < end; ++idx) { - struct page *page; - u8 *kaddr; - - /* Find or create the current page. (The page is locked.) */ - page = grab_cache_page(mapping, idx); - if (unlikely(!page)) { - ntfs_error(vol->sb, "Insufficient memory to grab " - "$LogFile page (index %lu).", idx); + if (!NVolLogFileEmpty(vol)) { + int err; + + err = ntfs_attr_set(NTFS_I(log_vi), 0, log_vi->i_size, 0xff); + if (unlikely(err)) { + ntfs_error(vol->sb, "Failed to fill $LogFile with " + "0xff bytes (error code %i).", err); return FALSE; } - /* - * Set all bytes in the page to 0xff. It doesn't matter if we - * go beyond i_size, because ntfs_writepage() will take care of - * that for us. - */ - kaddr = (u8*)kmap_atomic(page, KM_USER0); - memset(kaddr, 0xff, PAGE_CACHE_SIZE); - flush_dcache_page(page); - kunmap_atomic(kaddr, KM_USER0); - /* - * If the page has buffers, mark them uptodate since buffer - * state and not page state is definitive in 2.6 kernels. - */ - if (page_has_buffers(page)) { - struct buffer_head *bh, *head; - - bh = head = page_buffers(page); - do { - set_buffer_uptodate(bh); - } while ((bh = bh->b_this_page) != head); - } - /* Now that buffers are uptodate, set the page uptodate, too. */ - SetPageUptodate(page); - /* - * Set the page and all its buffers dirty and mark the inode - * dirty, too. The VM will write the page later on. - */ - set_page_dirty(page); - /* Finally unlock and release the page. */ - unlock_page(page); - page_cache_release(page); - } - /* We set the flag so we do not clear the log file again on remount. */ - NVolSetLogFileEmpty(vol); -done: + /* Set the flag so we do not have to do it again on remount. */ + NVolSetLogFileEmpty(vol); + } ntfs_debug("Done."); return TRUE; } -- cgit v1.2.3 From c412b415565954ae13a73da96eccb2ec4e1b00d9 Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Sat, 2 Oct 2004 00:18:26 +0100 Subject: NTFS: Remove unnecessary casts from LCN_* constants. Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 1 + fs/ntfs/aops.c | 6 +++--- fs/ntfs/attrib.c | 6 +++--- fs/ntfs/compress.c | 2 +- fs/ntfs/lcnalloc.c | 6 +++--- fs/ntfs/runlist.c | 32 ++++++++++++++++---------------- 6 files changed, 27 insertions(+), 26 deletions(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 1f505f16a316..8df36fb8965f 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -43,6 +43,7 @@ ToDo/Notes: - Implement the equivalent of memset() for an ntfs attribute in fs/ntfs/attrib.[hc]::ntfs_attr_set() and switch fs/ntfs/logfile.c::ntfs_empty_logfile() to using it. + - Remove unnecessary casts from LCN_* constants. 2.1.19 - Many cleanups, improvements, and a minor bug fix. diff --git a/fs/ntfs/aops.c b/fs/ntfs/aops.c index e343c50d80b1..f6e7922e7ab6 100644 --- a/fs/ntfs/aops.c +++ b/fs/ntfs/aops.c @@ -234,7 +234,7 @@ lock_retry_remap: rl++; lcn = ntfs_rl_vcn_to_lcn(rl, vcn); } else - lcn = (LCN)LCN_RL_NOT_MAPPED; + lcn = LCN_RL_NOT_MAPPED; /* Successful remap. */ if (lcn >= 0) { /* Setup buffer head to correct block. */ @@ -639,7 +639,7 @@ lock_retry_remap: rl++; lcn = ntfs_rl_vcn_to_lcn(rl, vcn); } else - lcn = (LCN)LCN_RL_NOT_MAPPED; + lcn = LCN_RL_NOT_MAPPED; /* Successful remap. */ if (lcn >= 0) { /* Setup buffer head to point to correct block. */ @@ -1404,7 +1404,7 @@ lock_retry_remap: rl++; lcn = ntfs_rl_vcn_to_lcn(rl, vcn); } else - lcn = (LCN)LCN_RL_NOT_MAPPED; + lcn = LCN_RL_NOT_MAPPED; if (unlikely(lcn < 0)) { /* * We extended the attribute allocation above. diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 3058a71dde4f..5537058e0c9e 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -140,7 +140,7 @@ lock_retry_remap: if (likely(rl && vcn >= rl[0].vcn)) { while (likely(rl->length)) { if (likely(vcn < rl[1].vcn)) { - if (likely(rl->lcn >= (LCN)LCN_HOLE)) { + if (likely(rl->lcn >= LCN_HOLE)) { ntfs_debug("Done."); return rl; } @@ -148,8 +148,8 @@ lock_retry_remap: } rl++; } - if (likely(rl->lcn != (LCN)LCN_RL_NOT_MAPPED)) { - if (likely(rl->lcn == (LCN)LCN_ENOENT)) + if (likely(rl->lcn != LCN_RL_NOT_MAPPED)) { + if (likely(rl->lcn == LCN_ENOENT)) err = -ENOENT; else err = -EIO; diff --git a/fs/ntfs/compress.c b/fs/ntfs/compress.c index 1adb24dcfcde..b4554e4bbbec 100644 --- a/fs/ntfs/compress.c +++ b/fs/ntfs/compress.c @@ -602,7 +602,7 @@ lock_retry_remap: rl++; lcn = ntfs_rl_vcn_to_lcn(rl, vcn); } else - lcn = (LCN)LCN_RL_NOT_MAPPED; + lcn = LCN_RL_NOT_MAPPED; ntfs_debug("Reading vcn = 0x%llx, lcn = 0x%llx.", (unsigned long long)vcn, (unsigned long long)lcn); diff --git a/fs/ntfs/lcnalloc.c b/fs/ntfs/lcnalloc.c index 12369fdf566a..5f291021a193 100644 --- a/fs/ntfs/lcnalloc.c +++ b/fs/ntfs/lcnalloc.c @@ -855,7 +855,7 @@ s64 __ntfs_cluster_free(struct inode *vi, const VCN start_vcn, s64 count, err = PTR_ERR(rl); goto err_out; } - if (unlikely(rl->lcn < (LCN)LCN_HOLE)) { + if (unlikely(rl->lcn < LCN_HOLE)) { if (!is_rollback) ntfs_error(vol->sb, "First runlist element has " "invalid lcn, aborting."); @@ -895,7 +895,7 @@ s64 __ntfs_cluster_free(struct inode *vi, const VCN start_vcn, s64 count, * free them. */ for (; rl->length && count != 0; ++rl) { - if (unlikely(rl->lcn < (LCN)LCN_HOLE)) { + if (unlikely(rl->lcn < LCN_HOLE)) { VCN vcn; /* @@ -926,7 +926,7 @@ s64 __ntfs_cluster_free(struct inode *vi, const VCN start_vcn, s64 count, "element."); goto err_out; } - if (unlikely(rl->lcn < (LCN)LCN_HOLE)) { + if (unlikely(rl->lcn < LCN_HOLE)) { if (!is_rollback) ntfs_error(vol->sb, "Runlist element " "has invalid lcn " diff --git a/fs/ntfs/runlist.c b/fs/ntfs/runlist.c index 29e28d844cc0..ac16bd594b28 100644 --- a/fs/ntfs/runlist.c +++ b/fs/ntfs/runlist.c @@ -530,7 +530,7 @@ runlist_element *ntfs_runlists_merge(runlist_element *drl, si = di = 0; /* Skip any unmapped start element(s) in the source runlist. */ - while (srl[si].length && srl[si].lcn < (LCN)LCN_HOLE) + while (srl[si].length && srl[si].lcn < LCN_HOLE) si++; /* Can't have an entirely unmapped source runlist. */ @@ -563,7 +563,7 @@ runlist_element *ntfs_runlists_merge(runlist_element *drl, for (dend = di; drl[dend].length; dend++) ; - if (srl[send].lcn == (LCN)LCN_ENOENT) + if (srl[send].lcn == LCN_ENOENT) marker_vcn = srl[marker = send].vcn; /* Scan to the last element with lcn >= LCN_HOLE. */ @@ -624,7 +624,7 @@ runlist_element *ntfs_runlists_merge(runlist_element *drl, "with LCN_ENOENT.", (unsigned long long) drl[ds].lcn); - drl[ds].lcn = (LCN)LCN_ENOENT; + drl[ds].lcn = LCN_ENOENT; goto finished; } /* @@ -632,11 +632,11 @@ runlist_element *ntfs_runlists_merge(runlist_element *drl, * @drl or extend an existing one before adding the * ENOENT terminator. */ - if (drl[ds].lcn == (LCN)LCN_ENOENT) { + if (drl[ds].lcn == LCN_ENOENT) { ds--; slots = 1; } - if (drl[ds].lcn != (LCN)LCN_RL_NOT_MAPPED) { + if (drl[ds].lcn != LCN_RL_NOT_MAPPED) { /* Add an unmapped runlist element. */ if (!slots) { /* FIXME/TODO: We need to have the @@ -651,7 +651,7 @@ runlist_element *ntfs_runlists_merge(runlist_element *drl, if (slots != 1) drl[ds].vcn = drl[ds - 1].vcn + drl[ds - 1].length; - drl[ds].lcn = (LCN)LCN_RL_NOT_MAPPED; + drl[ds].lcn = LCN_RL_NOT_MAPPED; /* We now used up a slot. */ slots--; } @@ -666,7 +666,7 @@ runlist_element *ntfs_runlists_merge(runlist_element *drl, goto critical_error; } drl[ds].vcn = marker_vcn; - drl[ds].lcn = (LCN)LCN_ENOENT; + drl[ds].lcn = LCN_ENOENT; drl[ds].length = (s64)0; } } @@ -753,8 +753,8 @@ runlist_element *ntfs_mapping_pairs_decompress(const ntfs_volume *vol, return ERR_PTR(-ENOMEM); /* Insert unmapped starting element if necessary. */ if (vcn) { - rl->vcn = (VCN)0; - rl->lcn = (LCN)LCN_RL_NOT_MAPPED; + rl->vcn = 0; + rl->lcn = LCN_RL_NOT_MAPPED; rl->length = vcn; rlpos++; } @@ -819,7 +819,7 @@ runlist_element *ntfs_mapping_pairs_decompress(const ntfs_volume *vol, * to LCN_HOLE. */ if (!(*buf & 0xf0)) - rl[rlpos].lcn = (LCN)LCN_HOLE; + rl[rlpos].lcn = LCN_HOLE; else { /* Get the lcn change which really can be negative. */ u8 b2 = *buf & 0xf; @@ -892,7 +892,7 @@ mpa_err: (unsigned long long)max_cluster); rl[rlpos].vcn = vcn; vcn += rl[rlpos].length = max_cluster - deltaxcn; - rl[rlpos].lcn = (LCN)LCN_RL_NOT_MAPPED; + rl[rlpos].lcn = LCN_RL_NOT_MAPPED; rlpos++; } else if (unlikely(deltaxcn > max_cluster)) { ntfs_error(vol->sb, "Corrupt attribute. deltaxcn = " @@ -901,9 +901,9 @@ mpa_err: (unsigned long long)max_cluster); goto mpa_err; } - rl[rlpos].lcn = (LCN)LCN_ENOENT; + rl[rlpos].lcn = LCN_ENOENT; } else /* Not the base extent. There may be more extents to follow. */ - rl[rlpos].lcn = (LCN)LCN_RL_NOT_MAPPED; + rl[rlpos].lcn = LCN_RL_NOT_MAPPED; /* Setup terminating runlist element. */ rl[rlpos].vcn = vcn; @@ -962,11 +962,11 @@ LCN ntfs_rl_vcn_to_lcn(const runlist_element *rl, const VCN vcn) * necessary. */ if (unlikely(!rl)) - return (LCN)LCN_RL_NOT_MAPPED; + return LCN_RL_NOT_MAPPED; /* Catch out of lower bounds vcn. */ if (unlikely(vcn < rl[0].vcn)) - return (LCN)LCN_ENOENT; + return LCN_ENOENT; for (i = 0; likely(rl[i].length); i++) { if (unlikely(vcn < rl[i+1].vcn)) { @@ -982,7 +982,7 @@ LCN ntfs_rl_vcn_to_lcn(const runlist_element *rl, const VCN vcn) if (likely(rl[i].lcn < (LCN)0)) return rl[i].lcn; /* Just in case... We could replace this with BUG() some day. */ - return (LCN)LCN_ENOENT; + return LCN_ENOENT; } /** -- cgit v1.2.3 From 659e947085d156fd766526aa27fada68d5df281c Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Sat, 2 Oct 2004 02:29:24 +0100 Subject: NTFS: Implement fs/ntfs/runlist.c::ntfs_rl_truncate_nolock(). Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 1 + fs/ntfs/runlist.c | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ fs/ntfs/runlist.h | 3 ++ 3 files changed, 140 insertions(+) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 8df36fb8965f..efd4ae717ab3 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -44,6 +44,7 @@ ToDo/Notes: fs/ntfs/attrib.[hc]::ntfs_attr_set() and switch fs/ntfs/logfile.c::ntfs_empty_logfile() to using it. - Remove unnecessary casts from LCN_* constants. + - Implement fs/ntfs/runlist.c::ntfs_rl_truncate(). 2.1.19 - Many cleanups, improvements, and a minor bug fix. diff --git a/fs/ntfs/runlist.c b/fs/ntfs/runlist.c index ac16bd594b28..b900f2fde18d 100644 --- a/fs/ntfs/runlist.c +++ b/fs/ntfs/runlist.c @@ -1321,3 +1321,139 @@ err_out: err = -EIO; return err; } + +/** + * ntfs_rl_truncate_nolock - truncate a runlist starting at a specified vcn + * @runlist: runlist to truncate + * @new_length: the new length of the runlist in VCNs + * + * Truncate the runlist described by @runlist as well as the memory buffer + * holding the runlist elements to a length of @new_length VCNs. + * + * If @new_length lies within the runlist, the runlist elements with VCNs of + * @new_length and above are discarded. + * + * If @new_length lies beyond the runlist, a sparse runlist element is added to + * the end of the runlist @runlist or if the last runlist element is a sparse + * one already, this is extended. + * + * Return 0 on success and -errno on error. + * + * Locking: The caller must hold @runlist->lock for writing. + */ +int ntfs_rl_truncate_nolock(const ntfs_volume *vol, runlist *const runlist, + const s64 new_length) +{ + runlist_element *rl; + int old_size; + + ntfs_debug("Entering for new_length 0x%llx.", (long long)new_length); + BUG_ON(!runlist); + BUG_ON(new_length < 0); + rl = runlist->rl; + if (unlikely(!rl)) { + /* + * Create a runlist consisting of a sparse runlist element of + * length @new_length followed by a terminator runlist element. + */ + rl = ntfs_malloc_nofs(PAGE_SIZE); + if (unlikely(!rl)) { + ntfs_error(vol->sb, "Not enough memory to allocate " + "runlist element buffer."); + return -ENOMEM; + } + runlist->rl = rl; + rl[1].length = rl->vcn = 0; + rl->lcn = LCN_HOLE; + rl[1].vcn = rl->length = new_length; + rl[1].lcn = LCN_ENOENT; + return 0; + } + BUG_ON(new_length < rl->vcn); + /* Find @new_length in the runlist. */ + while (likely(rl->length && new_length >= rl[1].vcn)) + rl++; + /* + * If not at the end of the runlist we need to shrink it. + * If at the end of the runlist we need to expand it. + */ + if (rl->length) { + runlist_element *trl; + BOOL is_end; + + ntfs_debug("Shrinking runlist."); + /* Determine the runlist size. */ + trl = rl + 1; + while (likely(trl->length)) + trl++; + old_size = trl - runlist->rl + 1; + /* Truncate the run. */ + rl->length = new_length - rl->vcn; + /* + * If a run was partially truncated, make the following runlist + * element a terminator. + */ + is_end = FALSE; + if (rl->length) { + rl++; + if (!rl->length) + is_end = TRUE; + rl->vcn = new_length; + rl->length = 0; + } + rl->lcn = LCN_ENOENT; + /* Reallocate memory if necessary. */ + if (!is_end) { + int new_size = rl - runlist->rl + 1; + rl = ntfs_rl_realloc(runlist->rl, old_size, new_size); + if (IS_ERR(rl)) + ntfs_warning(vol->sb, "Failed to shrink " + "runlist buffer. This just " + "wastes a bit of memory " + "temporarily so we ignore it " + "and return success."); + else + runlist->rl = rl; + } + } else if (likely(/* !rl->length && */ new_length > rl->vcn)) { + ntfs_debug("Expanding runlist."); + /* + * If there is a previous runlist element and it is a sparse + * one, extend it. Otherwise need to add a new, sparse runlist + * element. + */ + if ((rl > runlist->rl) && ((rl - 1)->lcn == LCN_HOLE)) + (rl - 1)->length = new_length - (rl - 1)->vcn; + else { + /* Determine the runlist size. */ + old_size = rl - runlist->rl + 1; + /* Reallocate memory if necessary. */ + rl = ntfs_rl_realloc(runlist->rl, old_size, + old_size + 1); + if (IS_ERR(rl)) { + ntfs_error(vol->sb, "Failed to expand runlist " + "buffer, aborting."); + return PTR_ERR(rl); + } + runlist->rl = rl; + /* + * Set @rl to the same runlist element in the new + * runlist as before in the old runlist. + */ + rl += old_size - 1; + /* Add a new, sparse runlist element. */ + rl->lcn = LCN_HOLE; + rl->length = new_length - rl->vcn; + /* Add a new terminator runlist element. */ + rl++; + rl->length = 0; + } + rl->vcn = new_length; + rl->lcn = LCN_ENOENT; + } else /* if (unlikely(!rl->length && new_length == rl->vcn)) */ { + /* Runlist already has same size as requested. */ + rl->lcn = LCN_ENOENT; + } + ntfs_debug("Done."); + return 0; +} diff --git a/fs/ntfs/runlist.h b/fs/ntfs/runlist.h index c3752c7a1409..7d82f4bd87d3 100644 --- a/fs/ntfs/runlist.h +++ b/fs/ntfs/runlist.h @@ -55,4 +55,7 @@ extern int ntfs_mapping_pairs_build(const ntfs_volume *vol, s8 *dst, const int dst_len, const runlist_element *rl, const VCN start_vcn, VCN *const stop_vcn); +extern int ntfs_rl_truncate_nolock(const ntfs_volume *vol, + runlist *const runlist, const s64 new_length); + #endif /* _LINUX_NTFS_RUNLIST_H */ -- cgit v1.2.3 From 801e2ec4c53fa7526e10b5cbb024fbd29bce94ed Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Sun, 3 Oct 2004 15:47:39 +0100 Subject: NTFS: Add MFT_RECORD_OLD as a copy of MFT_RECORD in fs/ntfs/layout.h and change MFT_RECORD to contain the NTFS 3.1+ specific fields. Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 2 ++ fs/ntfs/layout.h | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 74 insertions(+), 7 deletions(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 445eb4075ad2..2f89834fea9f 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -45,6 +45,8 @@ ToDo/Notes: fs/ntfs/logfile.c::ntfs_empty_logfile() to using it. - Remove unnecessary casts from LCN_* constants. - Implement fs/ntfs/runlist.c::ntfs_rl_truncate_nolock(). + - Add MFT_RECORD_OLD as a copy of MFT_RECORD in fs/ntfs/layout.h and + change MFT_RECORD to contain the NTFS 3.1+ specific fields. 2.1.20 - Fix two stupid bugs introduced in 2.1.18 release. diff --git a/fs/ntfs/layout.h b/fs/ntfs/layout.h index 57f3a8893f17..010189cd0ff2 100644 --- a/fs/ntfs/layout.h +++ b/fs/ntfs/layout.h @@ -260,7 +260,7 @@ typedef enum { enum { MFT_RECORD_IN_USE = const_cpu_to_le16(0x0001), MFT_RECORD_IS_DIRECTORY = const_cpu_to_le16(0x0002), -}; +} __attrobite__ ((__packed__)); typedef le16 MFT_RECORD_FLAGS; @@ -385,22 +385,87 @@ typedef struct { NOTE: Every time the mft record is reused this number is set to zero. NOTE: The first instance number is always 0. */ -/* sizeof() = 42 bytes */ -/* NTFS 3.1+ (Windows XP and above) introduce the following additions. */ -/* 42*/ //le16 reserved; /* Reserved/alignment. */ -/* 44*/ //le32 mft_record_number;/* Number of this mft record. */ +/* The below fields are specific to NTFS 3.1+ (Windows XP and above): */ +/* 42*/ le16 reserved; /* Reserved/alignment. */ +/* 44*/ le32 mft_record_number; /* Number of this mft record. */ /* sizeof() = 48 bytes */ /* * When (re)using the mft record, we place the update sequence array at this - * offset, i.e. before we start with the attributes. This also makes sense, + * offset, i.e. before we start with the attributes. This also makes sense, * otherwise we could run into problems with the update sequence array * containing in itself the last two bytes of a sector which would mean that - * multi sector transfer protection wouldn't work. As you can't protect data + * multi sector transfer protection wouldn't work. As you can't protect data * by overwriting it since you then can't get it back... * When reading we obviously use the data from the ntfs record header. */ } __attribute__ ((__packed__)) MFT_RECORD; +/* This is the version without the NTFS 3.1+ specific fields. */ +typedef struct { +/*Ofs*/ +/* 0 NTFS_RECORD; -- Unfolded here as gcc doesn't like unnamed structs. */ + NTFS_RECORD_TYPE magic; /* Usually the magic is "FILE". */ + le16 usa_ofs; /* See NTFS_RECORD definition above. */ + le16 usa_count; /* See NTFS_RECORD definition above. */ + +/* 8*/ le64 lsn; /* $LogFile sequence number for this record. + Changed every time the record is modified. */ +/* 16*/ le16 sequence_number; /* Number of times this mft record has been + reused. (See description for MFT_REF + above.) NOTE: The increment (skipping zero) + is done when the file is deleted. NOTE: If + this is zero it is left zero. */ +/* 18*/ le16 link_count; /* Number of hard links, i.e. the number of + directory entries referencing this record. + NOTE: Only used in mft base records. + NOTE: When deleting a directory entry we + check the link_count and if it is 1 we + delete the file. Otherwise we delete the + FILE_NAME_ATTR being referenced by the + directory entry from the mft record and + decrement the link_count. + FIXME: Careful with Win32 + DOS names! */ +/* 20*/ le16 attrs_offset; /* Byte offset to the first attribute in this + mft record from the start of the mft record. + NOTE: Must be aligned to 8-byte boundary. */ +/* 22*/ MFT_RECORD_FLAGS flags; /* Bit array of MFT_RECORD_FLAGS. When a file + is deleted, the MFT_RECORD_IN_USE flag is + set to zero. */ +/* 24*/ le32 bytes_in_use; /* Number of bytes used in this mft record. + NOTE: Must be aligned to 8-byte boundary. */ +/* 28*/ le32 bytes_allocated; /* Number of bytes allocated for this mft + record. This should be equal to the mft + record size. */ +/* 32*/ leMFT_REF base_mft_record;/* This is zero for base mft records. + When it is not zero it is a mft reference + pointing to the base mft record to which + this record belongs (this is then used to + locate the attribute list attribute present + in the base record which describes this + extension record and hence might need + modification when the extension record + itself is modified, also locating the + attribute list also means finding the other + potential extents, belonging to the non-base + mft record). */ +/* 40*/ le16 next_attr_instance;/* The instance number that will be assigned to + the next attribute added to this mft record. + NOTE: Incremented each time after it is used. + NOTE: Every time the mft record is reused + this number is set to zero. NOTE: The first + instance number is always 0. */ +/* sizeof() = 42 bytes */ +/* + * When (re)using the mft record, we place the update sequence array at this + * offset, i.e. before we start with the attributes. This also makes sense, + * otherwise we could run into problems with the update sequence array + * containing in itself the last two bytes of a sector which would mean that + * multi sector transfer protection wouldn't work. As you can't protect data + * by overwriting it since you then can't get it back... + * When reading we obviously use the data from the ntfs record header. + */ +} __attribute__ ((__packed__)) MFT_RECORD_OLD; + /* * System defined attributes (32-bit). Each attribute type has a corresponding * attribute name (Unicode string of maximum 64 character length) as described -- cgit v1.2.3 From c064136743dc896fc6180c565c2a227189aad336 Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Sun, 3 Oct 2004 15:52:14 +0100 Subject: NTFS: Add some debugging checks to fs/ntfs/inode.c::ntfs_truncate() and fix a typo in fs/ntfs/layout.h. Signed-off-by: Anton Altaparmakov --- fs/ntfs/inode.c | 2 ++ fs/ntfs/layout.h | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 900439db32e6..8c48de078c41 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -2289,6 +2289,8 @@ void ntfs_truncate(struct inode *vi) MFT_RECORD *m; int err; + BUG_ON(NInoAttr(ni)); + BUG_ON(ni->nr_extents < 0); m = map_mft_record(ni); if (IS_ERR(m)) { ntfs_error(vi->i_sb, "Failed to map mft record for inode 0x%lx " diff --git a/fs/ntfs/layout.h b/fs/ntfs/layout.h index 010189cd0ff2..94d3440d260d 100644 --- a/fs/ntfs/layout.h +++ b/fs/ntfs/layout.h @@ -260,7 +260,7 @@ typedef enum { enum { MFT_RECORD_IN_USE = const_cpu_to_le16(0x0001), MFT_RECORD_IS_DIRECTORY = const_cpu_to_le16(0x0002), -} __attrobite__ ((__packed__)); +} __attribute__ ((__packed__)); typedef le16 MFT_RECORD_FLAGS; -- cgit v1.2.3 From 680a90e8bd0ebbd8e82b01abca713a652c665768 Mon Sep 17 00:00:00 2001 From: Pete Zaitcev Date: Mon, 4 Oct 2004 00:34:36 -0700 Subject: [PATCH] USB: fix oops with latest ub driver in -mm tree On Fri, 1 Oct 2004 11:39:17 -0700 Greg KH wrote: > Pete, any ideas? Oh, it also happens on my UP laptop. >[...] > kernel BUG at kernel/timer.c:413! I have a suspicion. Actually, it was pointed to me by a kind soul before, but I forgot who he was, unfortunately. I'm not sure if this is the problem, but please try it if you can. It should apply on top of "latest". I really hate that word, but in this case I haven't got a version number. Signed-off-by: Greg Kroah-Hartman --- drivers/block/ub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/block/ub.c b/drivers/block/ub.c index efaf8264c345..0c3567159375 100644 --- a/drivers/block/ub.c +++ b/drivers/block/ub.c @@ -842,7 +842,6 @@ static void ub_urb_complete(struct urb *urb, struct pt_regs *pt) { struct ub_dev *sc = urb->context; - del_timer(&sc->work_timer); ub_complete(&sc->work_done); tasklet_schedule(&sc->tasklet); } @@ -853,6 +852,7 @@ static void ub_scsi_action(unsigned long _dev) unsigned long flags; spin_lock_irqsave(&sc->lock, flags); + del_timer(&sc->work_timer); ub_scsi_dispatch(sc); spin_unlock_irqrestore(&sc->lock, flags); } -- cgit v1.2.3 From 68353b96ab49df0b62b2b5d735ac4d20ea2b35d5 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Mon, 4 Oct 2004 22:32:34 +0100 Subject: USB: Generic USB ATM/DSL core and completed SpeedTouch driver This can now load the firmware and initialise the modem for itself, with no need for any userspace help (except for putting the firmware in /lib/firmware in the first place). The core packet I/O code is split out into a separate file where it can be used by drivers for some of the other similar modems. Signed-off-by: David Woodhouse Signed-off-by: Greg Kroah-Hartman --- drivers/usb/Kconfig | 2 + drivers/usb/Makefile | 4 +- drivers/usb/atm/Kconfig | 30 + drivers/usb/atm/Makefile | 7 + drivers/usb/atm/speedtch.c | 879 +++++++++++++++++++++++++++ drivers/usb/atm/usb_atm.c | 1192 +++++++++++++++++++++++++++++++++++++ drivers/usb/atm/usb_atm.h | 159 +++++ drivers/usb/misc/Kconfig | 12 - drivers/usb/misc/Makefile | 1 - drivers/usb/misc/speedtch.c | 1373 ------------------------------------------- 10 files changed, 2272 insertions(+), 1387 deletions(-) create mode 100644 drivers/usb/atm/Kconfig create mode 100644 drivers/usb/atm/Makefile create mode 100644 drivers/usb/atm/speedtch.c create mode 100644 drivers/usb/atm/usb_atm.c create mode 100644 drivers/usb/atm/usb_atm.h delete mode 100644 drivers/usb/misc/speedtch.c diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 0e805e4f3aa1..42ab55b9b538 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -91,6 +91,8 @@ source "drivers/usb/serial/Kconfig" source "drivers/usb/misc/Kconfig" +source "drivers/usb/atm/Kconfig" + source "drivers/usb/gadget/Kconfig" endmenu diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index e3787f35cd21..63a7f2d80f83 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -62,8 +62,10 @@ obj-$(CONFIG_USB_LCD) += misc/ obj-$(CONFIG_USB_LED) += misc/ obj-$(CONFIG_USB_LEGOTOWER) += misc/ obj-$(CONFIG_USB_RIO500) += misc/ -obj-$(CONFIG_USB_SPEEDTOUCH) += misc/ obj-$(CONFIG_USB_TEST) += misc/ obj-$(CONFIG_USB_TIGL) += misc/ obj-$(CONFIG_USB_USS720) += misc/ obj-$(CONFIG_USB_PHIDGETSERVO) += misc/ + +obj-$(CONFIG_USB_ATM) += atm/ +obj-$(CONFIG_USB_SPEEDTOUCH) += atm/ diff --git a/drivers/usb/atm/Kconfig b/drivers/usb/atm/Kconfig new file mode 100644 index 000000000000..0d9f5379b8cf --- /dev/null +++ b/drivers/usb/atm/Kconfig @@ -0,0 +1,30 @@ +# +# USB ATM driver configuration +# +comment "USB ATM/DSL drivers" + depends on USB + +config USB_ATM + tristate "Generic USB ATM/DSL core I/O support" + depends on USB && ATM + select CRC32 + default n + help + This provides a library which is used for packet I/O by USB DSL + modems, such as the SpeedTouch driver below. + + To compile this driver as a module, choose M here: the + module will be called usb_atm. + +config USB_SPEEDTOUCH + tristate "Alcatel Speedtouch USB support" + depends on USB && ATM + select USB_ATM + help + Say Y here if you have an Alcatel SpeedTouch USB or SpeedTouch 330 + modem. In order to use your modem you will need to install the + two parts of the firmware, extracted by the user space tools; see + for details. + + To compile this driver as a module, choose M here: the + module will be called speedtch. diff --git a/drivers/usb/atm/Makefile b/drivers/usb/atm/Makefile new file mode 100644 index 000000000000..9213b8b97587 --- /dev/null +++ b/drivers/usb/atm/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the rest of the USB drivers +# (the ones that don't fit into any other categories) +# + +obj-$(CONFIG_USB_ATM) += usb_atm.o +obj-$(CONFIG_USB_SPEEDTOUCH) += speedtch.o diff --git a/drivers/usb/atm/speedtch.c b/drivers/usb/atm/speedtch.c new file mode 100644 index 000000000000..29be861235e0 --- /dev/null +++ b/drivers/usb/atm/speedtch.c @@ -0,0 +1,879 @@ +/****************************************************************************** + * speedtch.c - Alcatel SpeedTouch USB xDSL modem driver + * + * Copyright (C) 2001, Alcatel + * Copyright (C) 2003, Duncan Sands + * Copyright (C) 2004, David Woodhouse + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usb_atm.h" + +/* +#define DEBUG +#define VERBOSE_DEBUG +*/ + +#if !defined (DEBUG) && defined (CONFIG_USB_DEBUG) +# define DEBUG +#endif + +#include + +#if defined(CONFIG_FW_LOADER) || defined(CONFIG_FW_LOADER_MODULE) +# define USE_FW_LOADER +#endif + +#ifdef DEBUG +#define DEBUG_ON(x) BUG_ON(x) +#else +#define DEBUG_ON(x) do { if (x); } while (0) +#endif + +#ifdef VERBOSE_DEBUG +static int udsl_print_packet (const unsigned char *data, int len); +#define PACKETDEBUG(arg...) udsl_print_packet (arg) +#define vdbg(arg...) dbg (arg) +#else +#define PACKETDEBUG(arg...) +#define vdbg(arg...) +#endif + +#define DRIVER_AUTHOR "Johan Verrept, Duncan Sands " +#define DRIVER_VERSION "1.8" +#define DRIVER_DESC "Alcatel SpeedTouch USB driver version " DRIVER_VERSION + +static const char speedtch_driver_name [] = "speedtch"; + +#define SPEEDTOUCH_VENDORID 0x06b9 +#define SPEEDTOUCH_PRODUCTID 0x4061 + +/* Timeout in jiffies */ +#define CTRL_TIMEOUT (2*HZ) +#define DATA_TIMEOUT (2*HZ) + +#define OFFSET_7 0 /* size 1 */ +#define OFFSET_b 1 /* size 8 */ +#define OFFSET_d 9 /* size 4 */ +#define OFFSET_e 13 /* size 1 */ +#define OFFSET_f 14 /* size 1 */ +#define TOTAL 15 + +#define SIZE_7 1 +#define SIZE_b 8 +#define SIZE_d 4 +#define SIZE_e 1 +#define SIZE_f 1 + +static int dl_512_first = 0; +static int sw_buffering = 0; + +module_param (dl_512_first, bool, 0444); +MODULE_PARM_DESC (dl_512_first, "Read 512 bytes before sending firmware"); + +module_param (sw_buffering, uint, 0444); +MODULE_PARM_DESC (sw_buffering, "Enable software buffering"); + +#define UDSL_IOCTL_LINE_UP 1 +#define UDSL_IOCTL_LINE_DOWN 2 + +#define SPEEDTCH_ENDPOINT_INT 0x81 +#define SPEEDTCH_ENDPOINT_DATA 0x07 +#define SPEEDTCH_ENDPOINT_FIRMWARE 0x05 + +#define hex2int(c) ( (c >= '0') && (c <= '9') ? (c - '0') : ((c & 0xf) + 9) ) + +static struct usb_device_id speedtch_usb_ids [] = { + { USB_DEVICE (SPEEDTOUCH_VENDORID, SPEEDTOUCH_PRODUCTID) }, + { } +}; + +MODULE_DEVICE_TABLE (usb, speedtch_usb_ids); + + +struct speedtch_instance_data { + struct udsl_instance_data u; + + u16 revision; + + /* Status */ + struct urb *int_urb; + unsigned char int_data[16]; + struct work_struct poll_work; + struct timer_list poll_timer; + char fwname[25]; +}; +/* USB */ + +static int speedtch_usb_probe (struct usb_interface *intf, const struct usb_device_id *id); +static void speedtch_usb_disconnect (struct usb_interface *intf); +static int speedtch_usb_ioctl (struct usb_interface *intf, unsigned int code, void *user_data); +static void speedtch_handle_int (struct urb *urb, struct pt_regs *regs); +static void speedtch_poll_status (struct speedtch_instance_data *instance); + +static struct usb_driver speedtch_usb_driver = { + .owner = THIS_MODULE, + .name = speedtch_driver_name, + .probe = speedtch_usb_probe, + .disconnect = speedtch_usb_disconnect, + .ioctl = speedtch_usb_ioctl, + .id_table = speedtch_usb_ids, +}; + + +/*************** +** firmware ** +***************/ + +static void speedtch_got_firmware (struct speedtch_instance_data *instance, int got_it) +{ + int err; + struct usb_interface *intf; + + down (&instance->u.serialize); /* vs self, speedtch_firmware_start */ + if (instance->u.status == UDSL_LOADED_FIRMWARE) + goto out; + if (!got_it) { + instance->u.status = UDSL_NO_FIRMWARE; + goto out; + } + if ((err = usb_set_interface (instance->u.usb_dev, 1, 1)) < 0) { + dbg ("speedtch_got_firmware: usb_set_interface returned %d!", err); + instance->u.status = UDSL_NO_FIRMWARE; + goto out; + } + + /* Set up interrupt endpoint */ + intf = usb_ifnum_to_if(instance->u.usb_dev, 0); + if (intf && !usb_driver_claim_interface (&speedtch_usb_driver, intf, NULL)) { + + instance->int_urb = usb_alloc_urb(0, GFP_KERNEL); + if (instance->int_urb) { + + usb_fill_int_urb(instance->int_urb, instance->u.usb_dev, + usb_rcvintpipe(instance->u.usb_dev, SPEEDTCH_ENDPOINT_INT), + instance->int_data, sizeof(instance->int_data), + speedtch_handle_int, instance, 50); + err = usb_submit_urb(instance->int_urb, GFP_KERNEL); + if (err) { + /* Doesn't matter; we'll poll anyway */ + dbg ("speedtch_got_firmware: Submission of interrupt URB failed %d", err); + usb_free_urb(instance->int_urb); + instance->int_urb = NULL; + usb_driver_release_interface (&speedtch_usb_driver, intf); + } + } + } + /* Start status polling */ + mod_timer(&instance->poll_timer, jiffies + (1*HZ)); + + instance->u.status = UDSL_LOADED_FIRMWARE; + tasklet_schedule (&instance->u.receive_tasklet); +out: + up (&instance->u.serialize); + wake_up_interruptible (&instance->u.firmware_waiters); +} + +static int speedtch_set_swbuff (struct speedtch_instance_data *instance, int state) +{ + struct usb_device *dev = instance->u.usb_dev; + int ret; + + ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + 0x32, 0x40, state?0x01:0x00, + 0x00, NULL, 0, 100); + if (ret < 0) { + printk("Warning: %sabling SW buffering: usb_control_msg returned %d\n", + state?"En":"Dis", ret); + return ret; + } + + dbg("speedtch_set_swbuff: %sbled SW buffering", state?"En":"Dis"); + return 0; +} + +static void speedtch_test_sequence(struct speedtch_instance_data *instance) +{ + struct usb_device *dev = instance->u.usb_dev; + unsigned char buf[10]; + int ret; + + /* URB 147 */ + buf[0] = 0x1c; buf[1] = 0x50; + ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + 0x01, 0x40, 0x0b, 0x00, buf, 2, 100); + if (ret < 0) + printk(KERN_WARNING "%s failed on URB147: %d\n", __func__, ret); + + /* URB 148 */ + buf[0] = 0x32; buf[1] = 0x00; + ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + 0x01, 0x40, 0x02, 0x00, buf, 2, 100); + if (ret < 0) + printk(KERN_WARNING "%s failed on URB148: %d\n", __func__, ret); + + /* URB 149 */ + buf[0] = 0x01; buf[1] = 0x00; buf[2] = 0x01; + ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + 0x01, 0x40, 0x03, 0x00, buf, 3, 100); + if (ret < 0) + printk(KERN_WARNING "%s failed on URB149: %d\n", __func__, ret); + + /* URB 150 */ + buf[0] = 0x01; buf[1] = 0x00; buf[2] = 0x01; + ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + 0x01, 0x40, 0x04, 0x00, buf, 3, 100); + if (ret < 0) + printk(KERN_WARNING "%s failed on URB150: %d\n", __func__, ret); +} + +static int speedtch_start_synchro (struct speedtch_instance_data *instance) +{ + struct usb_device *dev = instance->u.usb_dev; + unsigned char buf[2]; + int ret; + + ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + 0x12, 0xc0, 0x04, 0x00, + buf, sizeof(buf), CTRL_TIMEOUT); + if (ret < 0) { + printk(KERN_WARNING "SpeedTouch: Failed to start ADSL synchronisation: %d\n", ret); + return ret; + } + + dbg("speedtch_start_synchro: modem prodded. %d Bytes returned: %02x %02x", ret, buf[0], buf[1]); + return 0; +} + +static void speedtch_handle_int (struct urb *urb, struct pt_regs *regs) +{ + struct speedtch_instance_data *instance = urb->context; + unsigned int count = urb->actual_length; + int ret; + + /* The magic interrupt for "up state" */ + const static unsigned char up_int[6] = { 0xa1, 0x00, 0x01, 0x00, 0x00, 0x00}; + /* The magic interrupt for "down state" */ + const static unsigned char down_int[6] = { 0xa1, 0x00, 0x00, 0x00, 0x00, 0x00}; + + + switch (urb->status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dbg("%s - urb shutting down with status: %d", __func__, urb->status); + return; + default: + dbg("%s - nonzero urb status received: %d", __func__, urb->status); + goto exit; + } + + if (count < 6) { + dbg("%s - int packet too short", __func__); + goto exit; + } + + if (!memcmp(up_int, instance->int_data, 6)) { + del_timer(&instance->poll_timer); + printk(KERN_NOTICE "DSL line goes up\n"); + } else if (!memcmp(down_int, instance->int_data, 6)) { + del_timer(&instance->poll_timer); + printk(KERN_NOTICE "DSL line goes down\n"); + } else { + int i; + + printk(KERN_DEBUG "Unknown interrupt packet of %d bytes:", count); + for (i=0; i < count; i++) + printk(" %02x", instance->int_data[i]); + printk("\n"); + } + schedule_work(&instance->poll_work); + + exit: + rmb(); + if (!instance->int_urb) + return; + + ret = usb_submit_urb (urb, GFP_ATOMIC); + if (ret) + err ("%s - usb_submit_urb failed with result %d", + __func__, ret); +} + +static int speedtch_get_status(struct speedtch_instance_data *instance, unsigned char *buf) +{ + struct usb_device *dev = instance->u.usb_dev; + int ret; + + memset(buf,0,TOTAL); + + ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + 0x12, 0xc0, 0x07, 0x00, buf+OFFSET_7, SIZE_7, CTRL_TIMEOUT); + if (ret<0) { + dbg("MSG 7 failed"); + return(ret); + } + + ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + 0x12, 0xc0, 0x0b, 0x00, buf+OFFSET_b, SIZE_b, CTRL_TIMEOUT); + if (ret<0) { + dbg("MSG B failed"); + return(ret); + } + + ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + 0x12, 0xc0, 0x0d, 0x00, buf+OFFSET_d, SIZE_d, CTRL_TIMEOUT); + if (ret<0) { + dbg("MSG D failed"); + return(ret); + } + + ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + 0x01, 0xc0, 0x0e, 0x00, buf+OFFSET_e, SIZE_e, CTRL_TIMEOUT); + if (ret<0) { + dbg("MSG E failed"); + return(ret); + } + + ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + 0x01, 0xc0, 0x0f, 0x00, buf+OFFSET_f, SIZE_f, CTRL_TIMEOUT); + if (ret<0) { + dbg("MSG F failed"); + return(ret); + } + + return 0; +} + +static void speedtch_poll_status(struct speedtch_instance_data *instance) +{ + unsigned char buf[TOTAL]; + int ret; + + ret = speedtch_get_status(instance, buf); + if (ret) { + printk(KERN_WARNING "SpeedTouch: Error %d fetching device status\n", ret); + return; + } + + dbg("Line state %02x", buf[OFFSET_7]); + + switch (buf[OFFSET_7]) { + case 0: + if (instance->u.atm_dev->signal != ATM_PHY_SIG_LOST) { + instance->u.atm_dev->signal = ATM_PHY_SIG_LOST; + printk(KERN_NOTICE "ADSL line is down\n"); + } + break; + + case 0x08: + if (instance->u.atm_dev->signal != ATM_PHY_SIG_UNKNOWN) { + instance->u.atm_dev->signal = ATM_PHY_SIG_UNKNOWN; + printk(KERN_NOTICE "ADSL line is blocked?\n"); + } + break; + + case 0x10: + if (instance->u.atm_dev->signal != ATM_PHY_SIG_LOST) { + instance->u.atm_dev->signal = ATM_PHY_SIG_LOST; + printk(KERN_NOTICE "ADSL line is synchronising\n"); + } + break; + + case 0x20: + if (instance->u.atm_dev->signal != ATM_PHY_SIG_FOUND) { + int down_speed = buf[OFFSET_b] | (buf[OFFSET_b+1]<<8) + | (buf[OFFSET_b+2]<<16) | (buf[OFFSET_b+3]<<24); + int up_speed = buf[OFFSET_b+4] | (buf[OFFSET_b+5]<<8) + | (buf[OFFSET_b+6]<<16) | (buf[OFFSET_b+7]<<24); + + if(!(down_speed & 0x0000ffff) && + !(up_speed & 0x0000ffff)) { + down_speed>>=16; + up_speed>>=16; + } + instance->u.atm_dev->link_rate = down_speed * 1000 / 424; + instance->u.atm_dev->signal = ATM_PHY_SIG_FOUND; + + printk(KERN_NOTICE "ADSL line is up (%d Kib/s down | %d Kib/s up)\n", + down_speed, up_speed); + } + break; + + default: + if (instance->u.atm_dev->signal != ATM_PHY_SIG_UNKNOWN) { + instance->u.atm_dev->signal = ATM_PHY_SIG_UNKNOWN; + printk(KERN_NOTICE "Unknown line state %02x\n", buf[OFFSET_7]); + } + break; + } +} + +static void speedtch_timer_poll (unsigned long data) +{ + struct speedtch_instance_data *instance = (struct speedtch_instance_data *) data; + + schedule_work(&instance->poll_work); + mod_timer(&instance->poll_timer, jiffies + (5*HZ)); +} + +static void speedtch_firmware_load(const struct firmware *fw1, void *context) +{ + const struct firmware *fw2; + unsigned char *buffer; + struct speedtch_instance_data *instance = context; + struct usb_interface *intf; + struct usb_device *dev = instance->u.usb_dev; + int actual_length, ret; + int pg; + + dbg ("speedtch_firmware_load"); + + ret = -1; + + BUG_ON(!instance); + + if (!(intf = usb_ifnum_to_if (dev, 2))) { + dbg ("speedtch_firmware_load: interface not found!"); + goto fail; + } + + if (!(buffer = kmalloc (0x1000, GFP_KERNEL))) { + dbg ("speedtch_firmware_load: no memory for buffer!"); + goto fail; + } + + if (!fw1) { + dbg ("speedtch_firmware_load: no firmware 'speedtch_boot_rev%d.%02x", + instance->revision >> 8, instance->revision & 0xff); + + snprintf(instance->fwname, sizeof(instance->fwname), "speedtch_boot_rev%d", + instance->revision >> 8); + + ret = request_firmware(&fw1, instance->fwname, &dev->dev); + if (ret < 0) { + dbg ("speedtch_firmware_load: no firmware '%s'", instance->fwname); + + strlcpy(instance->fwname, "speedtch_boot", sizeof(instance->fwname)); + ret = request_firmware(&fw1, instance->fwname, &dev->dev); + } + if (ret < 0) { + dbg ("speedtch_firmware_load: no firmware '%s'", instance->fwname); + printk(KERN_INFO "speedtch: No boot firmware found. Assuming userspace firmware loader\n"); + goto fail_buf; + } + } + snprintf(instance->fwname, sizeof(instance->fwname), "speedtch_main_rev%d.%02x", + instance->revision >> 8, instance->revision & 0xff); + ret = request_firmware(&fw2, instance->fwname, &dev->dev); + if (ret < 0) { + dbg ("speedtch_firmware_load: no firmware '%s'", instance->fwname); + snprintf(instance->fwname, sizeof(instance->fwname), "speedtch_main_rev%d", + instance->revision >> 8); + + ret = request_firmware(&fw2, instance->fwname, &dev->dev); + } + if (ret < 0) { + dbg ("speedtch_firmware_load: no firmware '%s'", instance->fwname); + + strlcpy(instance->fwname, "speedtch_main", sizeof(instance->fwname)); + ret = request_firmware(&fw2, instance->fwname, &dev->dev); + } + if (ret < 0) { + dbg ("speedtch_firmware_load: no firmware '%s'", instance->fwname); + printk(KERN_INFO "speedtch: No main firmware found. Assuming userspace firmware loader\n"); + goto fail_buf; + } + + /* OK, we have the firmware. So try to claim interface #2 and actually + try uploading it. There's a slight possibility that the userspace + modem_run could be running too, and may have beaten us to it */ + if ((ret = usb_driver_claim_interface (&speedtch_usb_driver, intf, NULL)) < 0) { + dbg ("speedtch_firmware_start: interface in use (%d)!", ret); + goto fail_fw2; + } + + /* URB 7 */ + if (dl_512_first) { /* some modems need a read before writing the firmware */ + ret = usb_bulk_msg (instance->u.usb_dev, + usb_rcvbulkpipe (instance->u.usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE), + buffer, + 0x200, + &actual_length, + 2 * HZ); + + if (ret < 0 && ret != -ETIMEDOUT) + dbg ("speedtch_firmware_load: read BLOCK0 from modem failed (%d)!", ret); + else + dbg("speedtch_firmware_load: BLOCK0 downloaded (%d bytes)", ret); + } + + /* URB 8 : both leds are static green */ + for (pg = 0; pg * 0x1000 < fw1->size; pg++) { + int thislen = min_t(int, 0x1000, fw1->size - (pg*0x1000)); + memcpy(buffer, fw1->data + (pg*0x1000), thislen); + + ret = usb_bulk_msg (instance->u.usb_dev, + usb_sndbulkpipe (instance->u.usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE), + buffer, + thislen, + &actual_length, + DATA_TIMEOUT); + + if (ret < 0) { + dbg ("speedtch_firmware_load: write BLOCK1 to modem failed (%d)!", ret); + goto fail_release; + } + dbg("speedtch_firmware_load: BLOCK1 uploaded (%d bytes)", fw1->size); + } + + /* USB led blinking green, ADSL led off */ + + /* URB 11 */ + ret = usb_bulk_msg (instance->u.usb_dev, + usb_rcvbulkpipe (instance->u.usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE), + buffer, + 0x200, + &actual_length, + DATA_TIMEOUT); + + if (ret < 0) { + dbg ("speedtch_firmware_load: read BLOCK2 from modem failed (%d)!", ret); + goto fail_release; + } + dbg("speedtch_firmware_load: BLOCK2 downloaded (%d bytes)", actual_length); + + /* URBs 12 to 139 - USB led blinking green, ADSL led off */ + for (pg = 0; pg * 0x1000 < fw2->size; pg++) { + int thislen = min_t(int, 0x1000, fw2->size - (pg*0x1000)); + memcpy(buffer, fw2->data + (pg*0x1000), thislen); + + ret = usb_bulk_msg (instance->u.usb_dev, + usb_sndbulkpipe (instance->u.usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE), + buffer, + thislen, + &actual_length, + DATA_TIMEOUT); + + if (ret < 0) { + dbg ("speedtch_firmware_load: write BLOCK3 to modem failed (%d)!", ret); + goto fail_release; + } + } + dbg("speedtch_firmware_load: BLOCK3 uploaded (%d bytes)", fw2->size); + + /* USB led static green, ADSL led static red */ + + /* URB 142 */ + ret = usb_bulk_msg (instance->u.usb_dev, + usb_rcvbulkpipe (instance->u.usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE), + buffer, + 0x200, + &actual_length, + DATA_TIMEOUT); + + if (ret < 0) { + dbg("speedtch_firmware_load: read BLOCK4 from modem failed (%d)!", ret); + goto fail_release; + } + + /* success */ + dbg("speedtch_firmware_load: BLOCK4 downloaded (%d bytes)", actual_length); + + /* Delay to allow firmware to start up. We can do this here + because we're in our own kernel thread anyway. */ + msleep(1000); + + /* Enable software buffering, if requested */ + if (sw_buffering) + speedtch_set_swbuff(instance, 1); + + /* Magic spell; don't ask us what this does */ + speedtch_test_sequence(instance); + + /* Start modem synchronisation */ + if (speedtch_start_synchro(instance)) + dbg("speedtch_start_synchro: failed\n"); + + speedtch_got_firmware(instance, 1); + + goto fail_fw2; /* The got_firmware(0) is a NOP anyway */ + + fail_release: + /* We only release interface #2 if loading the firmware failed; we don't + do it if we succeeded. This prevents the userspace modem_run tool from + trying to load the firmware itself */ + usb_driver_release_interface (&speedtch_usb_driver, intf); + fail_fw2: + release_firmware(fw2); + fail_buf: + kfree (buffer); + fail: + speedtch_got_firmware (instance, 0); + udsl_put_instance(&instance->u); +} + +static void speedtch_firmware_start (struct speedtch_instance_data *instance) +{ +#ifdef USE_FW_LOADER + int ret; +#endif + + dbg ("speedtch_firmware_start"); + + down (&instance->u.serialize); /* vs self, speedtch_got_firmware */ + + if (instance->u.status >= UDSL_LOADING_FIRMWARE) { + up (&instance->u.serialize); + return; + } + + instance->u.status = UDSL_LOADING_FIRMWARE; + up (&instance->u.serialize); + + udsl_get_instance(&instance->u); + +#ifdef USE_FW_LOADER + snprintf(instance->fwname, sizeof(instance->fwname), "speedtch_boot_rev%d.%02x", + instance->revision >> 8, instance->revision & 0xff); + printk("Look for %s\n", instance->fwname); + ret = request_firmware_nowait (THIS_MODULE, + instance->fwname, + &instance->u.usb_dev->dev, + instance, + speedtch_firmware_load); + + if (ret >= 0) + return; /* OK */ + + dbg ("speedtch_firmware_start: request_firmware_nowait failed (%d)!", ret); + + + /* Just pretend it never happened... hope modem_run happens */ +#endif /* USE_FW_LOADER */ + speedtch_got_firmware (instance, 0); + udsl_put_instance(&instance->u); +} + +static int speedtch_firmware_wait (struct udsl_instance_data *instance) +{ + speedtch_firmware_start ((void *) instance); + + if (wait_event_interruptible (instance->firmware_waiters, instance->status != UDSL_LOADING_FIRMWARE) < 0) + return -ERESTARTSYS; + + return (instance->status == UDSL_LOADED_FIRMWARE) ? 0 : -EAGAIN; +} + +/********** +** USB ** +**********/ + +static int speedtch_usb_ioctl (struct usb_interface *intf, unsigned int code, void *user_data) +{ + struct speedtch_instance_data *instance = usb_get_intfdata (intf); + + dbg ("speedtch_usb_ioctl entered"); + + if (!instance) { + dbg ("speedtch_usb_ioctl: NULL instance!"); + return -ENODEV; + } + + switch (code) { + case UDSL_IOCTL_LINE_UP: + instance->u.atm_dev->signal = ATM_PHY_SIG_FOUND; + speedtch_got_firmware (instance, 1); + return (instance->u.status == UDSL_LOADED_FIRMWARE) ? 0 : -EIO; + case UDSL_IOCTL_LINE_DOWN: + instance->u.atm_dev->signal = ATM_PHY_SIG_LOST; + return 0; + default: + return -ENOTTY; + } +} + +static int speedtch_usb_probe (struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + int ifnum = intf->altsetting->desc.bInterfaceNumber; + struct speedtch_instance_data *instance; + unsigned char mac_str [13]; + int ret, i; + char buf7[SIZE_7]; + + dbg ("speedtch_usb_probe: trying device with vendor=0x%x, product=0x%x, ifnum %d", + dev->descriptor.idVendor, dev->descriptor.idProduct, ifnum); + + if ((dev->descriptor.bDeviceClass != USB_CLASS_VENDOR_SPEC) || + (dev->descriptor.idVendor != SPEEDTOUCH_VENDORID) || + (dev->descriptor.idProduct != SPEEDTOUCH_PRODUCTID) || (ifnum != 1)) + return -ENODEV; + + dbg ("speedtch_usb_probe: device accepted"); + + /* instance init */ + if (!(instance = kmalloc (sizeof (struct speedtch_instance_data), GFP_KERNEL))) { + dbg ("speedtch_usb_probe: no memory for instance data!"); + return -ENOMEM; + } + + memset (instance, 0, sizeof (struct speedtch_instance_data)); + + if ((ret = usb_set_interface (dev, 0, 0)) < 0) + goto fail; + + if ((ret = usb_set_interface (dev, 2, 0)) < 0) + goto fail; + + instance->u.data_endpoint = SPEEDTCH_ENDPOINT_DATA; + instance->u.firmware_wait = speedtch_firmware_wait; + instance->u.driver_name = speedtch_driver_name; + + ret = udsl_instance_setup(dev, &instance->u); + if (ret) + goto fail; + + init_timer(&instance->poll_timer); + instance->poll_timer.function = speedtch_timer_poll; + instance->poll_timer.data = (unsigned long)instance; + + INIT_WORK(&instance->poll_work, (void *)speedtch_poll_status, instance); + + switch (dev->descriptor.bcdDevice) { + case 0x0000: + case 0x0200: + case 0x0400: + case 0x0401: + instance->revision = dev->descriptor.bcdDevice; + break; + default: + instance->revision = 0x200; + printk(KERN_INFO "Unexpected SpeedTouch revision %04x, treating as Rev 2.00.\n", + dev->descriptor.bcdDevice); + break; + } + + /* set MAC address, it is stored in the serial number */ + memset (instance->u.atm_dev->esi, 0, sizeof (instance->u.atm_dev->esi)); + if (usb_string (dev, dev->descriptor.iSerialNumber, mac_str, sizeof (mac_str)) == 12) + for (i = 0; i < 6; i++) + instance->u.atm_dev->esi [i] = (hex2int (mac_str [i * 2]) * 16) + (hex2int (mac_str [i * 2 + 1])); + + + /* First check whether the modem already seems to be alive */ + ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + 0x12, 0xc0, 0x07, 0x00, buf7, SIZE_7, HZ/2); + + if (ret == SIZE_7) { + dbg("firmware appears to be already loaded"); + speedtch_got_firmware(instance, 1); + speedtch_poll_status(instance); + } else { + speedtch_firmware_start (instance); + } + + usb_set_intfdata (intf, instance); + + return 0; + +fail: + kfree (instance); + + return -ENOMEM; +} + +static void speedtch_usb_disconnect (struct usb_interface *intf) +{ + struct speedtch_instance_data *instance = usb_get_intfdata (intf); + + dbg ("speedtch_usb_disconnect entered"); + + if (!instance) { + dbg ("speedtch_usb_disconnect: NULL instance!"); + return; + } + + if (instance->int_urb) { + struct urb *int_urb = instance->int_urb; + instance->int_urb = NULL; + wmb(); + usb_unlink_urb(int_urb); + usb_free_urb(int_urb); + } + + instance->int_data[0] = 1; + del_timer_sync(&instance->poll_timer); + wmb(); + flush_scheduled_work(); + + udsl_instance_disconnect(&instance->u); + + /* clean up */ + usb_set_intfdata (intf, NULL); + udsl_put_instance(&instance->u); +} + + +/*********** +** init ** +***********/ + +static int __init speedtch_usb_init (void) +{ + dbg ("speedtch_usb_init: driver version " DRIVER_VERSION); + + return usb_register (&speedtch_usb_driver); +} + +static void __exit speedtch_usb_cleanup (void) +{ + dbg ("speedtch_usb_cleanup entered"); + + usb_deregister (&speedtch_usb_driver); +} + +module_init (speedtch_usb_init); +module_exit (speedtch_usb_cleanup); + +MODULE_AUTHOR (DRIVER_AUTHOR); +MODULE_DESCRIPTION (DRIVER_DESC); +MODULE_LICENSE ("GPL"); +MODULE_VERSION (DRIVER_VERSION); diff --git a/drivers/usb/atm/usb_atm.c b/drivers/usb/atm/usb_atm.c new file mode 100644 index 000000000000..9a792ab71e49 --- /dev/null +++ b/drivers/usb/atm/usb_atm.c @@ -0,0 +1,1192 @@ +/****************************************************************************** + * usb_atm.c - Generic USB xDSL driver core + * + * Copyright (C) 2001, Alcatel + * Copyright (C) 2003, Duncan Sands, SolNegro, Josep Comas + * Copyright (C) 2004, David Woodhouse + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ******************************************************************************/ + +/* + * Written by Johan Verrept, maintained by Duncan Sands (duncan.sands@free.fr) + * + * 1.7+: - See the check-in logs + * + * 1.6: - No longer opens a connection if the firmware is not loaded + * - Added support for the speedtouch 330 + * - Removed the limit on the number of devices + * - Module now autoloads on device plugin + * - Merged relevant parts of sarlib + * - Replaced the kernel thread with a tasklet + * - New packet transmission code + * - Changed proc file contents + * - Fixed all known SMP races + * - Many fixes and cleanups + * - Various fixes by Oliver Neukum (oliver@neukum.name) + * + * 1.5A: - Version for inclusion in 2.5 series kernel + * - Modifications by Richard Purdie (rpurdie@rpsys.net) + * - made compatible with kernel 2.5.6 onwards by changing + * udsl_usb_send_data_context->urb to a pointer and adding code + * to alloc and free it + * - remove_wait_queue() added to udsl_atm_processqueue_thread() + * + * 1.5: - fixed memory leak when atmsar_decode_aal5 returned NULL. + * (reported by stephen.robinson@zen.co.uk) + * + * 1.4: - changed the spin_lock() under interrupt to spin_lock_irqsave() + * - unlink all active send urbs of a vcc that is being closed. + * + * 1.3.1: - added the version number + * + * 1.3: - Added multiple send urb support + * - fixed memory leak and vcc->tx_inuse starvation bug + * when not enough memory left in vcc. + * + * 1.2: - Fixed race condition in udsl_usb_send_data() + * 1.1: - Turned off packet debugging + * + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usb_atm.h" + +/* +#define DEBUG +#define VERBOSE_DEBUG +*/ + +#if !defined (DEBUG) && defined (CONFIG_USB_DEBUG) +# define DEBUG +#endif + +#include + +#ifdef DEBUG +#define DEBUG_ON(x) BUG_ON(x) +#else +#define DEBUG_ON(x) do { if (x); } while (0) +#endif + +#ifdef VERBOSE_DEBUG +static int udsl_print_packet (const unsigned char *data, int len); +#define PACKETDEBUG(arg...) udsl_print_packet (arg) +#define vdbg(arg...) dbg (arg) +#else +#define PACKETDEBUG(arg...) +#define vdbg(arg...) +#endif + +#define DRIVER_AUTHOR "Johan Verrept, Duncan Sands " +#define DRIVER_VERSION "1.8" +#define DRIVER_DESC "Alcatel SpeedTouch USB driver version " DRIVER_VERSION + +static unsigned int num_rcv_urbs = UDSL_DEFAULT_RCV_URBS; +static unsigned int num_snd_urbs = UDSL_DEFAULT_SND_URBS; +static unsigned int num_rcv_bufs = UDSL_DEFAULT_RCV_BUFS; +static unsigned int num_snd_bufs = UDSL_DEFAULT_SND_BUFS; +static unsigned int rcv_buf_size = UDSL_DEFAULT_RCV_BUF_SIZE; +static unsigned int snd_buf_size = UDSL_DEFAULT_SND_BUF_SIZE; + +module_param (num_rcv_urbs, uint, 0444); +MODULE_PARM_DESC (num_rcv_urbs, "Number of urbs used for reception (range: 0-" __MODULE_STRING (UDSL_MAX_RCV_URBS) ", default: " __MODULE_STRING (UDSL_DEFAULT_RCV_URBS) ")"); + +module_param (num_snd_urbs, uint, 0444); +MODULE_PARM_DESC (num_snd_urbs, "Number of urbs used for transmission (range: 0-" __MODULE_STRING (UDSL_MAX_SND_URBS) ", default: " __MODULE_STRING (UDSL_DEFAULT_SND_URBS) ")"); + +module_param (num_rcv_bufs, uint, 0444); +MODULE_PARM_DESC (num_rcv_bufs, "Number of buffers used for reception (range: 0-" __MODULE_STRING (UDSL_MAX_RCV_BUFS) ", default: " __MODULE_STRING (UDSL_DEFAULT_RCV_BUFS) ")"); + +module_param (num_snd_bufs, uint, 0444); +MODULE_PARM_DESC (num_snd_bufs, "Number of buffers used for transmission (range: 0-" __MODULE_STRING (UDSL_MAX_SND_BUFS) ", default: " __MODULE_STRING (UDSL_DEFAULT_SND_BUFS) ")"); + +module_param (rcv_buf_size, uint, 0444); +MODULE_PARM_DESC (rcv_buf_size, "Size of the buffers used for reception (range: 0-" __MODULE_STRING (UDSL_MAX_RCV_BUF_SIZE) ", default: " __MODULE_STRING (UDSL_DEFAULT_RCV_BUF_SIZE) ")"); + +module_param (snd_buf_size, uint, 0444); +MODULE_PARM_DESC (snd_buf_size, "Size of the buffers used for transmission (range: 0-" __MODULE_STRING (UDSL_MAX_SND_BUF_SIZE) ", default: " __MODULE_STRING (UDSL_DEFAULT_SND_BUF_SIZE) ")"); + +/* ATM */ + +static void udsl_atm_dev_close (struct atm_dev *dev); +static int udsl_atm_open (struct atm_vcc *vcc); +static void udsl_atm_close (struct atm_vcc *vcc); +static int udsl_atm_ioctl (struct atm_dev *dev, unsigned int cmd, void __user *arg); +static int udsl_atm_send (struct atm_vcc *vcc, struct sk_buff *skb); +static int udsl_atm_proc_read (struct atm_dev *atm_dev, loff_t *pos, char *page); + +static struct atmdev_ops udsl_atm_devops = { + .dev_close = udsl_atm_dev_close, + .open = udsl_atm_open, + .close = udsl_atm_close, + .ioctl = udsl_atm_ioctl, + .send = udsl_atm_send, + .proc_read = udsl_atm_proc_read, + .owner = THIS_MODULE, +}; + + +/*********** +** misc ** +***********/ + +static inline void udsl_pop (struct atm_vcc *vcc, struct sk_buff *skb) +{ + if (vcc->pop) + vcc->pop (vcc, skb); + else + dev_kfree_skb (skb); +} + + +/************* +** decode ** +*************/ + +static inline struct udsl_vcc_data *udsl_find_vcc (struct udsl_instance_data *instance, short vpi, int vci) +{ + struct udsl_vcc_data *vcc; + + list_for_each_entry (vcc, &instance->vcc_list, list) + if ((vcc->vci == vci) && (vcc->vpi == vpi)) + return vcc; + return NULL; +} + +static void udsl_extract_cells (struct udsl_instance_data *instance, unsigned char *source, unsigned int howmany) +{ + struct udsl_vcc_data *cached_vcc = NULL; + struct atm_vcc *vcc; + struct sk_buff *sarb; + struct udsl_vcc_data *vcc_data; + int cached_vci = 0; + unsigned int i; + int pti; + int vci; + short cached_vpi = 0; + short vpi; + + for (i = 0; i < howmany; i++, source += ATM_CELL_SIZE + instance->rcv_padding) { + vpi = ((source [0] & 0x0f) << 4) | (source [1] >> 4); + vci = ((source [1] & 0x0f) << 12) | (source [2] << 4) | (source [3] >> 4); + pti = (source [3] & 0x2) != 0; + + vdbg ("udsl_extract_cells: vpi %hd, vci %d, pti %d", vpi, vci, pti); + + if (cached_vcc && (vci == cached_vci) && (vpi == cached_vpi)) + vcc_data = cached_vcc; + else if ((vcc_data = udsl_find_vcc (instance, vpi, vci))) { + cached_vcc = vcc_data; + cached_vpi = vpi; + cached_vci = vci; + } else { + dbg ("udsl_extract_cells: unknown vpi/vci (%hd/%d)!", vpi, vci); + continue; + } + + vcc = vcc_data->vcc; + sarb = vcc_data->sarb; + + if (sarb->tail + ATM_CELL_PAYLOAD > sarb->end) { + dbg ("udsl_extract_cells: buffer overrun (sarb->len %u, vcc: 0x%p)!", sarb->len, vcc); + /* discard cells already received */ + skb_trim (sarb, 0); + } + + memcpy (sarb->tail, source + ATM_CELL_HEADER, ATM_CELL_PAYLOAD); + __skb_put (sarb, ATM_CELL_PAYLOAD); + + if (pti) { + struct sk_buff *skb; + unsigned int length; + unsigned int pdu_length; + + length = (source [ATM_CELL_SIZE - 6] << 8) + source [ATM_CELL_SIZE - 5]; + + /* guard against overflow */ + if (length > ATM_MAX_AAL5_PDU) { + dbg ("udsl_extract_cells: bogus length %u (vcc: 0x%p)!", length, vcc); + atomic_inc (&vcc->stats->rx_err); + goto out; + } + + pdu_length = UDSL_NUM_CELLS (length) * ATM_CELL_PAYLOAD; + + if (sarb->len < pdu_length) { + dbg ("udsl_extract_cells: bogus pdu_length %u (sarb->len: %u, vcc: 0x%p)!", pdu_length, sarb->len, vcc); + atomic_inc (&vcc->stats->rx_err); + goto out; + } + + if (crc32_be (~0, sarb->tail - pdu_length, pdu_length) != 0xc704dd7b) { + dbg ("udsl_extract_cells: packet failed crc check (vcc: 0x%p)!", vcc); + atomic_inc (&vcc->stats->rx_err); + goto out; + } + + vdbg ("udsl_extract_cells: got packet (length: %u, pdu_length: %u, vcc: 0x%p)", length, pdu_length, vcc); + + if (!(skb = dev_alloc_skb (length))) { + dbg ("udsl_extract_cells: no memory for skb (length: %u)!", length); + atomic_inc (&vcc->stats->rx_drop); + goto out; + } + + vdbg ("udsl_extract_cells: allocated new sk_buff (skb: 0x%p, skb->truesize: %u)", skb, skb->truesize); + + if (!atm_charge (vcc, skb->truesize)) { + dbg ("udsl_extract_cells: failed atm_charge (skb->truesize: %u)!", skb->truesize); + dev_kfree_skb (skb); + goto out; /* atm_charge increments rx_drop */ + } + + memcpy (skb->data, sarb->tail - pdu_length, length); + __skb_put (skb, length); + + vdbg ("udsl_extract_cells: sending skb 0x%p, skb->len %u, skb->truesize %u", skb, skb->len, skb->truesize); + + PACKETDEBUG (skb->data, skb->len); + + vcc->push (vcc, skb); + + atomic_inc (&vcc->stats->rx); +out: + skb_trim (sarb, 0); + } + } +} + + +/************* +** encode ** +*************/ + +static const unsigned char zeros [ATM_CELL_PAYLOAD]; + +static void udsl_groom_skb (struct atm_vcc *vcc, struct sk_buff *skb) +{ + struct udsl_control *ctrl = UDSL_SKB (skb); + unsigned int zero_padding; + u32 crc; + + ctrl->atm_data.vcc = vcc; + ctrl->cell_header [0] = vcc->vpi >> 4; + ctrl->cell_header [1] = (vcc->vpi << 4) | (vcc->vci >> 12); + ctrl->cell_header [2] = vcc->vci >> 4; + ctrl->cell_header [3] = vcc->vci << 4; + ctrl->cell_header [4] = 0xec; + + ctrl->num_cells = UDSL_NUM_CELLS (skb->len); + ctrl->num_entire = skb->len / ATM_CELL_PAYLOAD; + + zero_padding = ctrl->num_cells * ATM_CELL_PAYLOAD - skb->len - ATM_AAL5_TRAILER; + + if (ctrl->num_entire + 1 < ctrl->num_cells) + ctrl->pdu_padding = zero_padding - (ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER); + else + ctrl->pdu_padding = zero_padding; + + ctrl->aal5_trailer [0] = 0; /* UU = 0 */ + ctrl->aal5_trailer [1] = 0; /* CPI = 0 */ + ctrl->aal5_trailer [2] = skb->len >> 8; + ctrl->aal5_trailer [3] = skb->len; + + crc = crc32_be (~0, skb->data, skb->len); + crc = crc32_be (crc, zeros, zero_padding); + crc = crc32_be (crc, ctrl->aal5_trailer, 4); + crc = ~crc; + + ctrl->aal5_trailer [4] = crc >> 24; + ctrl->aal5_trailer [5] = crc >> 16; + ctrl->aal5_trailer [6] = crc >> 8; + ctrl->aal5_trailer [7] = crc; +} + +static unsigned int udsl_write_cells(struct udsl_instance_data *instance, unsigned int howmany, + struct sk_buff *skb, unsigned char **target_p) +{ + struct udsl_control *ctrl = UDSL_SKB (skb); + unsigned char *target = *target_p; + unsigned int nc, ne, i; + + vdbg ("udsl_write_cells: howmany=%u, skb->len=%d, num_cells=%u, num_entire=%u, pdu_padding=%u", howmany, skb->len, ctrl->num_cells, ctrl->num_entire, ctrl->pdu_padding); + + nc = ctrl->num_cells; + ne = min (howmany, ctrl->num_entire); + + for (i = 0; i < ne; i++) { + memcpy (target, ctrl->cell_header, ATM_CELL_HEADER); + target += ATM_CELL_HEADER; + memcpy (target, skb->data, ATM_CELL_PAYLOAD); + target += ATM_CELL_PAYLOAD; + if (instance->snd_padding) { + memset (target, 0, instance->snd_padding); + target += instance->snd_padding; + } + __skb_pull (skb, ATM_CELL_PAYLOAD); + } + + ctrl->num_entire -= ne; + + if (!(ctrl->num_cells -= ne) || !(howmany -= ne)) + goto out; + + if (instance->snd_padding) { + memset (target, 0, instance->snd_padding); + target += instance->snd_padding; + } + memcpy (target, ctrl->cell_header, ATM_CELL_HEADER); + target += ATM_CELL_HEADER; + memcpy (target, skb->data, skb->len); + target += skb->len; + __skb_pull (skb, skb->len); + memset (target, 0, ctrl->pdu_padding); + target += ctrl->pdu_padding; + + if (--ctrl->num_cells) { + if (!--howmany) { + ctrl->pdu_padding = ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER; + goto out; + } + + memcpy (target, ctrl->cell_header, ATM_CELL_HEADER); + target += ATM_CELL_HEADER; + memset (target, 0, ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER); + target += ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER; + + DEBUG_ON (--ctrl->num_cells); + } + + memcpy (target, ctrl->aal5_trailer, ATM_AAL5_TRAILER); + target += ATM_AAL5_TRAILER; + /* set pti bit in last cell */ + *(target + 3 - ATM_CELL_SIZE) |= 0x2; + if (instance->snd_padding) { + memset (target, 0, instance->snd_padding); + target += instance->snd_padding; + } +out: + *target_p = target; + return nc - ctrl->num_cells; +} + + +/************** +** receive ** +**************/ + +static void udsl_complete_receive (struct urb *urb, struct pt_regs *regs) +{ + struct udsl_receive_buffer *buf; + struct udsl_instance_data *instance; + struct udsl_receiver *rcv; + unsigned long flags; + + if (!urb || !(rcv = urb->context)) { + dbg ("udsl_complete_receive: bad urb!"); + return; + } + + instance = rcv->instance; + buf = rcv->buffer; + + buf->filled_cells = urb->actual_length / (ATM_CELL_SIZE + instance->rcv_padding); + + vdbg ("udsl_complete_receive: urb 0x%p, status %d, actual_length %d, filled_cells %u, rcv 0x%p, buf 0x%p", urb, urb->status, urb->actual_length, buf->filled_cells, rcv, buf); + + DEBUG_ON (buf->filled_cells > rcv_buf_size); + + /* may not be in_interrupt() */ + spin_lock_irqsave (&instance->receive_lock, flags); + list_add (&rcv->list, &instance->spare_receivers); + list_add_tail (&buf->list, &instance->filled_receive_buffers); + if (likely (!urb->status)) + tasklet_schedule (&instance->receive_tasklet); + spin_unlock_irqrestore (&instance->receive_lock, flags); +} + +static void udsl_process_receive (unsigned long data) +{ + struct udsl_receive_buffer *buf; + struct udsl_instance_data *instance = (struct udsl_instance_data *) data; + struct udsl_receiver *rcv; + int err; + +made_progress: + while (!list_empty (&instance->spare_receive_buffers)) { + spin_lock_irq (&instance->receive_lock); + if (list_empty (&instance->spare_receivers)) { + spin_unlock_irq (&instance->receive_lock); + break; + } + rcv = list_entry (instance->spare_receivers.next, struct udsl_receiver, list); + list_del (&rcv->list); + spin_unlock_irq (&instance->receive_lock); + + buf = list_entry (instance->spare_receive_buffers.next, struct udsl_receive_buffer, list); + list_del (&buf->list); + + rcv->buffer = buf; + + usb_fill_bulk_urb (rcv->urb, + instance->usb_dev, + usb_rcvbulkpipe (instance->usb_dev, instance->data_endpoint), + buf->base, + rcv_buf_size * (ATM_CELL_SIZE + instance->rcv_padding), + udsl_complete_receive, + rcv); + + vdbg ("udsl_process_receive: sending urb 0x%p, rcv 0x%p, buf 0x%p", rcv->urb, rcv, buf); + + if ((err = usb_submit_urb(rcv->urb, GFP_ATOMIC)) < 0) { + dbg ("udsl_process_receive: urb submission failed (%d)!", err); + list_add (&buf->list, &instance->spare_receive_buffers); + spin_lock_irq (&instance->receive_lock); + list_add (&rcv->list, &instance->spare_receivers); + spin_unlock_irq (&instance->receive_lock); + break; + } + } + + spin_lock_irq (&instance->receive_lock); + if (list_empty (&instance->filled_receive_buffers)) { + spin_unlock_irq (&instance->receive_lock); + return; /* done - no more buffers */ + } + buf = list_entry (instance->filled_receive_buffers.next, struct udsl_receive_buffer, list); + list_del (&buf->list); + spin_unlock_irq (&instance->receive_lock); + vdbg ("udsl_process_receive: processing buf 0x%p", buf); + udsl_extract_cells (instance, buf->base, buf->filled_cells); + list_add (&buf->list, &instance->spare_receive_buffers); + goto made_progress; +} + + +/*********** +** send ** +***********/ + +static void udsl_complete_send (struct urb *urb, struct pt_regs *regs) +{ + struct udsl_instance_data *instance; + struct udsl_sender *snd; + unsigned long flags; + + if (!urb || !(snd = urb->context) || !(instance = snd->instance)) { + dbg ("udsl_complete_send: bad urb!"); + return; + } + + vdbg ("udsl_complete_send: urb 0x%p, status %d, snd 0x%p, buf 0x%p", urb, urb->status, snd, snd->buffer); + + /* may not be in_interrupt() */ + spin_lock_irqsave (&instance->send_lock, flags); + list_add (&snd->list, &instance->spare_senders); + list_add (&snd->buffer->list, &instance->spare_send_buffers); + tasklet_schedule (&instance->send_tasklet); + spin_unlock_irqrestore (&instance->send_lock, flags); +} + +static void udsl_process_send (unsigned long data) +{ + struct udsl_send_buffer *buf; + struct udsl_instance_data *instance = (struct udsl_instance_data *) data; + struct sk_buff *skb; + struct udsl_sender *snd; + int err; + unsigned int num_written; + +made_progress: + spin_lock_irq (&instance->send_lock); + while (!list_empty (&instance->spare_senders)) { + if (!list_empty (&instance->filled_send_buffers)) { + buf = list_entry (instance->filled_send_buffers.next, struct udsl_send_buffer, list); + list_del (&buf->list); + } else if ((buf = instance->current_buffer)) { + instance->current_buffer = NULL; + } else /* all buffers empty */ + break; + + snd = list_entry (instance->spare_senders.next, struct udsl_sender, list); + list_del (&snd->list); + spin_unlock_irq (&instance->send_lock); + + snd->buffer = buf; + usb_fill_bulk_urb (snd->urb, + instance->usb_dev, + usb_sndbulkpipe (instance->usb_dev, instance->data_endpoint), + buf->base, + (snd_buf_size - buf->free_cells) * (ATM_CELL_SIZE + instance->snd_padding), + udsl_complete_send, + snd); + + vdbg ("udsl_process_send: submitting urb 0x%p (%d cells), snd 0x%p, buf 0x%p", snd->urb, snd_buf_size - buf->free_cells, snd, buf); + + if ((err = usb_submit_urb(snd->urb, GFP_ATOMIC)) < 0) { + dbg ("udsl_process_send: urb submission failed (%d)!", err); + spin_lock_irq (&instance->send_lock); + list_add (&snd->list, &instance->spare_senders); + spin_unlock_irq (&instance->send_lock); + list_add (&buf->list, &instance->filled_send_buffers); + return; /* bail out */ + } + + spin_lock_irq (&instance->send_lock); + } /* while */ + spin_unlock_irq (&instance->send_lock); + + if (!instance->current_skb && !(instance->current_skb = skb_dequeue (&instance->sndqueue))) + return; /* done - no more skbs */ + + skb = instance->current_skb; + + if (!(buf = instance->current_buffer)) { + spin_lock_irq (&instance->send_lock); + if (list_empty (&instance->spare_send_buffers)) { + instance->current_buffer = NULL; + spin_unlock_irq (&instance->send_lock); + return; /* done - no more buffers */ + } + buf = list_entry (instance->spare_send_buffers.next, struct udsl_send_buffer, list); + list_del (&buf->list); + spin_unlock_irq (&instance->send_lock); + + buf->free_start = buf->base; + buf->free_cells = snd_buf_size; + + instance->current_buffer = buf; + } + + num_written = udsl_write_cells(instance, buf->free_cells, skb, &buf->free_start); + + vdbg ("udsl_process_send: wrote %u cells from skb 0x%p to buffer 0x%p", num_written, skb, buf); + + if (!(buf->free_cells -= num_written)) { + list_add_tail (&buf->list, &instance->filled_send_buffers); + instance->current_buffer = NULL; + } + + vdbg ("udsl_process_send: buffer contains %d cells, %d left", snd_buf_size - buf->free_cells, buf->free_cells); + + if (!UDSL_SKB (skb)->num_cells) { + struct atm_vcc *vcc = UDSL_SKB (skb)->atm_data.vcc; + + udsl_pop (vcc, skb); + instance->current_skb = NULL; + + atomic_inc (&vcc->stats->tx); + } + + goto made_progress; +} + +static void udsl_cancel_send (struct udsl_instance_data *instance, struct atm_vcc *vcc) +{ + struct sk_buff *skb, *n; + + dbg ("udsl_cancel_send entered"); + spin_lock_irq (&instance->sndqueue.lock); + for (skb = instance->sndqueue.next, n = skb->next; skb != (struct sk_buff *)&instance->sndqueue; skb = n, n = skb->next) + if (UDSL_SKB (skb)->atm_data.vcc == vcc) { + dbg ("udsl_cancel_send: popping skb 0x%p", skb); + __skb_unlink (skb, &instance->sndqueue); + udsl_pop (vcc, skb); + } + spin_unlock_irq (&instance->sndqueue.lock); + + tasklet_disable (&instance->send_tasklet); + if ((skb = instance->current_skb) && (UDSL_SKB (skb)->atm_data.vcc == vcc)) { + dbg ("udsl_cancel_send: popping current skb (0x%p)", skb); + instance->current_skb = NULL; + udsl_pop (vcc, skb); + } + tasklet_enable (&instance->send_tasklet); + dbg ("udsl_cancel_send done"); +} + +static int udsl_atm_send (struct atm_vcc *vcc, struct sk_buff *skb) +{ + struct udsl_instance_data *instance = vcc->dev->dev_data; + int err; + + vdbg ("udsl_atm_send called (skb 0x%p, len %u)", skb, skb->len); + + if (!instance) { + dbg ("udsl_atm_send: NULL data!"); + err = -ENODEV; + goto fail; + } + + if (vcc->qos.aal != ATM_AAL5) { + dbg ("udsl_atm_send: unsupported ATM type %d!", vcc->qos.aal); + err = -EINVAL; + goto fail; + } + + if (skb->len > ATM_MAX_AAL5_PDU) { + dbg ("udsl_atm_send: packet too long (%d vs %d)!", skb->len, ATM_MAX_AAL5_PDU); + err = -EINVAL; + goto fail; + } + + PACKETDEBUG (skb->data, skb->len); + + udsl_groom_skb (vcc, skb); + skb_queue_tail (&instance->sndqueue, skb); + tasklet_schedule (&instance->send_tasklet); + + return 0; + +fail: + udsl_pop (vcc, skb); + return err; +} + + +/******************** +** bean counting ** +********************/ + +static void udsl_destroy_instance(struct kref *kref) +{ + struct udsl_instance_data *instance = container_of (kref, struct udsl_instance_data, refcount); + + tasklet_kill (&instance->receive_tasklet); + tasklet_kill (&instance->send_tasklet); + usb_put_dev (instance->usb_dev); + kfree (instance); +} + + + +void udsl_get_instance(struct udsl_instance_data *instance) +{ + kref_get (&instance->refcount); +} + +void udsl_put_instance(struct udsl_instance_data *instance) +{ + kref_put (&instance->refcount, udsl_destroy_instance); +} + + +/********** +** ATM ** +**********/ + +static void udsl_atm_dev_close (struct atm_dev *dev) +{ + struct udsl_instance_data *instance = dev->dev_data; + + dev->dev_data = NULL; + udsl_put_instance (instance); +} + +static int udsl_atm_proc_read (struct atm_dev *atm_dev, loff_t *pos, char *page) +{ + struct udsl_instance_data *instance = atm_dev->dev_data; + int left = *pos; + + if (!instance) { + dbg ("udsl_atm_proc_read: NULL instance!"); + return -ENODEV; + } + + if (!left--) + return sprintf (page, "%s\n", instance->description); + + if (!left--) + return sprintf (page, "MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", + atm_dev->esi [0], atm_dev->esi [1], atm_dev->esi [2], + atm_dev->esi [3], atm_dev->esi [4], atm_dev->esi [5]); + + if (!left--) + return sprintf (page, "AAL5: tx %d ( %d err ), rx %d ( %d err, %d drop )\n", + atomic_read (&atm_dev->stats.aal5.tx), + atomic_read (&atm_dev->stats.aal5.tx_err), + atomic_read (&atm_dev->stats.aal5.rx), + atomic_read (&atm_dev->stats.aal5.rx_err), + atomic_read (&atm_dev->stats.aal5.rx_drop)); + + if (!left--) { + switch (atm_dev->signal) { + case ATM_PHY_SIG_FOUND: + sprintf (page, "Line up"); + break; + case ATM_PHY_SIG_LOST: + sprintf (page, "Line down"); + break; + default: + sprintf (page, "Line state unknown"); + break; + } + + if (instance->usb_dev->state == USB_STATE_NOTATTACHED) + strcat (page, ", disconnected\n"); + else { + if (instance->status == UDSL_LOADED_FIRMWARE) + strcat (page, ", firmware loaded\n"); + else if (instance->status == UDSL_LOADING_FIRMWARE) + strcat (page, ", firmware loading\n"); + else + strcat (page, ", no firmware\n"); + } + + return strlen (page); + } + + return 0; +} + +static int udsl_atm_open (struct atm_vcc *vcc) +{ + struct udsl_instance_data *instance = vcc->dev->dev_data; + struct udsl_vcc_data *new; + unsigned int max_pdu; + int vci = vcc->vci; + short vpi = vcc->vpi; + int err; + + dbg ("udsl_atm_open: vpi %hd, vci %d", vpi, vci); + + if (!instance) { + dbg ("udsl_atm_open: NULL data!"); + return -ENODEV; + } + + /* only support AAL5 */ + if ((vcc->qos.aal != ATM_AAL5) || (vcc->qos.rxtp.max_sdu < 0) || (vcc->qos.rxtp.max_sdu > ATM_MAX_AAL5_PDU)) { + dbg ("udsl_atm_open: unsupported ATM type %d!", vcc->qos.aal); + return -EINVAL; + } + + if (instance->firmware_wait && + (err = instance->firmware_wait (instance)) < 0) { + dbg ("udsl_atm_open: firmware not loaded (%d)!", err); + return err; + } + + down (&instance->serialize); /* vs self, udsl_atm_close */ + + if (udsl_find_vcc (instance, vpi, vci)) { + dbg ("udsl_atm_open: %hd/%d already in use!", vpi, vci); + up (&instance->serialize); + return -EADDRINUSE; + } + + if (!(new = kmalloc (sizeof (struct udsl_vcc_data), GFP_KERNEL))) { + dbg ("udsl_atm_open: no memory for vcc_data!"); + up (&instance->serialize); + return -ENOMEM; + } + + memset (new, 0, sizeof (struct udsl_vcc_data)); + new->vcc = vcc; + new->vpi = vpi; + new->vci = vci; + + /* udsl_extract_cells requires at least one cell */ + max_pdu = max (1, UDSL_NUM_CELLS (vcc->qos.rxtp.max_sdu)) * ATM_CELL_PAYLOAD; + if (!(new->sarb = alloc_skb (max_pdu, GFP_KERNEL))) { + dbg ("udsl_atm_open: no memory for SAR buffer!"); + kfree (new); + up (&instance->serialize); + return -ENOMEM; + } + + vcc->dev_data = new; + + tasklet_disable (&instance->receive_tasklet); + list_add (&new->list, &instance->vcc_list); + tasklet_enable (&instance->receive_tasklet); + + set_bit (ATM_VF_ADDR, &vcc->flags); + set_bit (ATM_VF_PARTIAL, &vcc->flags); + set_bit (ATM_VF_READY, &vcc->flags); + + up (&instance->serialize); + + tasklet_schedule (&instance->receive_tasklet); + + dbg ("udsl_atm_open: allocated vcc data 0x%p (max_pdu: %u)", new, max_pdu); + + return 0; +} + +static void udsl_atm_close (struct atm_vcc *vcc) +{ + struct udsl_instance_data *instance = vcc->dev->dev_data; + struct udsl_vcc_data *vcc_data = vcc->dev_data; + + dbg ("udsl_atm_close called"); + + if (!instance || !vcc_data) { + dbg ("udsl_atm_close: NULL data!"); + return; + } + + dbg ("udsl_atm_close: deallocating vcc 0x%p with vpi %d vci %d", vcc_data, vcc_data->vpi, vcc_data->vci); + + udsl_cancel_send (instance, vcc); + + down (&instance->serialize); /* vs self, udsl_atm_open */ + + tasklet_disable (&instance->receive_tasklet); + list_del (&vcc_data->list); + tasklet_enable (&instance->receive_tasklet); + + kfree_skb (vcc_data->sarb); + vcc_data->sarb = NULL; + + kfree (vcc_data); + vcc->dev_data = NULL; + + vcc->vpi = ATM_VPI_UNSPEC; + vcc->vci = ATM_VCI_UNSPEC; + clear_bit (ATM_VF_READY, &vcc->flags); + clear_bit (ATM_VF_PARTIAL, &vcc->flags); + clear_bit (ATM_VF_ADDR, &vcc->flags); + + up (&instance->serialize); + + dbg ("udsl_atm_close successful"); +} + +static int udsl_atm_ioctl (struct atm_dev *dev, unsigned int cmd, void __user *arg) +{ + switch (cmd) { + case ATM_QUERYLOOP: + return put_user (ATM_LM_NONE, (int __user *)arg) ? -EFAULT : 0; + default: + return -ENOIOCTLCMD; + } +} + + +/********** +** USB ** +**********/ + +int udsl_instance_setup(struct usb_device *dev, struct udsl_instance_data *instance) +{ + char *buf; + int i, length; + + kref_init (&instance->refcount); /* one for USB */ + udsl_get_instance (instance); /* one for ATM */ + + + init_MUTEX (&instance->serialize); + + instance->usb_dev = dev; + + INIT_LIST_HEAD (&instance->vcc_list); + + instance->status = UDSL_NO_FIRMWARE; + init_waitqueue_head (&instance->firmware_waiters); + + spin_lock_init (&instance->receive_lock); + INIT_LIST_HEAD (&instance->spare_receivers); + INIT_LIST_HEAD (&instance->filled_receive_buffers); + + tasklet_init (&instance->receive_tasklet, udsl_process_receive, (unsigned long) instance); + INIT_LIST_HEAD (&instance->spare_receive_buffers); + + skb_queue_head_init (&instance->sndqueue); + + spin_lock_init (&instance->send_lock); + INIT_LIST_HEAD (&instance->spare_senders); + INIT_LIST_HEAD (&instance->spare_send_buffers); + + tasklet_init (&instance->send_tasklet, udsl_process_send, (unsigned long) instance); + INIT_LIST_HEAD (&instance->filled_send_buffers); + + /* receive init */ + for (i = 0; i < num_rcv_urbs; i++) { + struct udsl_receiver *rcv = &(instance->receivers [i]); + + if (!(rcv->urb = usb_alloc_urb (0, GFP_KERNEL))) { + dbg ("udsl_usb_probe: no memory for receive urb %d!", i); + goto fail; + } + + rcv->instance = instance; + + list_add (&rcv->list, &instance->spare_receivers); + } + + for (i = 0; i < num_rcv_bufs; i++) { + struct udsl_receive_buffer *buf = &(instance->receive_buffers [i]); + + if (!(buf->base = kmalloc (rcv_buf_size * (ATM_CELL_SIZE + instance->rcv_padding), GFP_KERNEL))) { + dbg ("udsl_usb_probe: no memory for receive buffer %d!", i); + goto fail; + } + + list_add (&buf->list, &instance->spare_receive_buffers); + } + + /* send init */ + for (i = 0; i < num_snd_urbs; i++) { + struct udsl_sender *snd = &(instance->senders [i]); + + if (!(snd->urb = usb_alloc_urb (0, GFP_KERNEL))) { + dbg ("udsl_usb_probe: no memory for send urb %d!", i); + goto fail; + } + + snd->instance = instance; + + list_add (&snd->list, &instance->spare_senders); + } + + for (i = 0; i < num_snd_bufs; i++) { + struct udsl_send_buffer *buf = &(instance->send_buffers [i]); + + if (!(buf->base = kmalloc (snd_buf_size * (ATM_CELL_SIZE + instance->snd_padding), GFP_KERNEL))) { + dbg ("udsl_usb_probe: no memory for send buffer %d!", i); + goto fail; + } + + list_add (&buf->list, &instance->spare_send_buffers); + } + + /* ATM init */ + if (!(instance->atm_dev = atm_dev_register (instance->driver_name, &udsl_atm_devops, -1, NULL))) { + dbg ("udsl_usb_probe: failed to register ATM device!"); + goto fail; + } + + instance->atm_dev->ci_range.vpi_bits = ATM_CI_MAX; + instance->atm_dev->ci_range.vci_bits = ATM_CI_MAX; + instance->atm_dev->signal = ATM_PHY_SIG_UNKNOWN; + + /* temp init ATM device, set to 128kbit */ + instance->atm_dev->link_rate = 128 * 1000 / 424; + + /* device description */ + buf = instance->description; + length = sizeof (instance->description); + + if ((i = usb_string (dev, dev->descriptor.iProduct, buf, length)) < 0) + goto finish; + + buf += i; + length -= i; + + i = scnprintf (buf, length, " ("); + buf += i; + length -= i; + + if (length <= 0 || (i = usb_make_path (dev, buf, length)) < 0) + goto finish; + + buf += i; + length -= i; + + snprintf (buf, length, ")"); + +finish: + /* ready for ATM callbacks */ + wmb (); + instance->atm_dev->dev_data = instance; + + usb_get_dev (dev); + + return 0; + +fail: + for (i = 0; i < num_snd_bufs; i++) + kfree (instance->send_buffers [i].base); + + for (i = 0; i < num_snd_urbs; i++) + usb_free_urb (instance->senders [i].urb); + + for (i = 0; i < num_rcv_bufs; i++) + kfree (instance->receive_buffers [i].base); + + for (i = 0; i < num_rcv_urbs; i++) + usb_free_urb (instance->receivers [i].urb); + + return -ENOMEM; +} + +void udsl_instance_disconnect (struct udsl_instance_data *instance) +{ + struct list_head *pos; + unsigned int count; + int result, i; + + dbg ("udsl_instance_disconnect entered"); + + if (!instance) { + dbg ("udsl_instance_disconnect: NULL instance!"); + return; + } + + /* receive finalize */ + tasklet_disable (&instance->receive_tasklet); + + for (i = 0; i < num_rcv_urbs; i++) + if ((result = usb_unlink_urb (instance->receivers [i].urb)) < 0) + dbg ("udsl_instance_disconnect: usb_unlink_urb on receive urb %d returned %d!", i, result); + + /* wait for completion handlers to finish */ + do { + count = 0; + spin_lock_irq (&instance->receive_lock); + list_for_each (pos, &instance->spare_receivers) + DEBUG_ON (++count > num_rcv_urbs); + spin_unlock_irq (&instance->receive_lock); + + dbg ("udsl_instance_disconnect: found %u spare receivers", count); + + if (count == num_rcv_urbs) + break; + + set_current_state (TASK_RUNNING); + schedule (); + } while (1); + + /* no need to take the spinlock */ + INIT_LIST_HEAD (&instance->filled_receive_buffers); + INIT_LIST_HEAD (&instance->spare_receive_buffers); + + tasklet_enable (&instance->receive_tasklet); + + for (i = 0; i < num_rcv_urbs; i++) + usb_free_urb (instance->receivers [i].urb); + + for (i = 0; i < num_rcv_bufs; i++) + kfree (instance->receive_buffers [i].base); + + /* send finalize */ + tasklet_disable (&instance->send_tasklet); + + for (i = 0; i < num_snd_urbs; i++) + if ((result = usb_unlink_urb (instance->senders [i].urb)) < 0) + dbg ("udsl_instance_disconnect: usb_unlink_urb on send urb %d returned %d!", i, result); + + /* wait for completion handlers to finish */ + do { + count = 0; + spin_lock_irq (&instance->send_lock); + list_for_each (pos, &instance->spare_senders) + DEBUG_ON (++count > num_snd_urbs); + spin_unlock_irq (&instance->send_lock); + + dbg ("udsl_instance_disconnect: found %u spare senders", count); + + if (count == num_snd_urbs) + break; + + set_current_state (TASK_RUNNING); + schedule (); + } while (1); + + /* no need to take the spinlock */ + INIT_LIST_HEAD (&instance->spare_senders); + INIT_LIST_HEAD (&instance->spare_send_buffers); + instance->current_buffer = NULL; + + tasklet_enable (&instance->send_tasklet); + + for (i = 0; i < num_snd_urbs; i++) + usb_free_urb (instance->senders [i].urb); + + for (i = 0; i < num_snd_bufs; i++) + kfree (instance->send_buffers [i].base); + + /* ATM finalize */ + shutdown_atm_dev (instance->atm_dev); +} + +EXPORT_SYMBOL_GPL(udsl_get_instance); +EXPORT_SYMBOL_GPL(udsl_put_instance); +EXPORT_SYMBOL_GPL(udsl_instance_setup); +EXPORT_SYMBOL_GPL(udsl_instance_disconnect); + +/*********** +** init ** +***********/ + +static int __init udsl_usb_init (void) +{ + dbg ("udsl_usb_init: driver version " DRIVER_VERSION); + + if (sizeof (struct udsl_control) > sizeof (((struct sk_buff *)0)->cb)) { + printk (KERN_ERR __FILE__ ": unusable with this kernel!\n"); + return -EIO; + } + + if ((num_rcv_urbs > UDSL_MAX_RCV_URBS) || (num_snd_urbs > UDSL_MAX_SND_URBS) || + (num_rcv_bufs > UDSL_MAX_RCV_BUFS) || (num_snd_bufs > UDSL_MAX_SND_BUFS) || + (rcv_buf_size > UDSL_MAX_RCV_BUF_SIZE) || (snd_buf_size > UDSL_MAX_SND_BUF_SIZE)) + return -EINVAL; + + return 0; +} + +module_init (udsl_usb_init); + +MODULE_AUTHOR (DRIVER_AUTHOR); +MODULE_DESCRIPTION (DRIVER_DESC); +MODULE_LICENSE ("GPL"); +MODULE_VERSION (DRIVER_VERSION); + + +/************ +** debug ** +************/ + +#ifdef VERBOSE_DEBUG +static int udsl_print_packet (const unsigned char *data, int len) +{ + unsigned char buffer [256]; + int i = 0, j = 0; + + for (i = 0; i < len;) { + buffer [0] = '\0'; + sprintf (buffer, "%.3d :", i); + for (j = 0; (j < 16) && (i < len); j++, i++) { + sprintf (buffer, "%s %2.2x", buffer, data [i]); + } + dbg ("%s", buffer); + } + return i; +} +#endif diff --git a/drivers/usb/atm/usb_atm.h b/drivers/usb/atm/usb_atm.h new file mode 100644 index 000000000000..1eef3571d312 --- /dev/null +++ b/drivers/usb/atm/usb_atm.h @@ -0,0 +1,159 @@ +/****************************************************************************** + * usb_atm.h - Generic USB xDSL driver core + * + * Copyright (C) 2001, Alcatel + * Copyright (C) 2003, Duncan Sands, SolNegro, Josep Comas + * Copyright (C) 2004, David Woodhouse + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ******************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#define UDSL_MAX_RCV_URBS 4 +#define UDSL_MAX_SND_URBS 4 +#define UDSL_MAX_RCV_BUFS 8 +#define UDSL_MAX_SND_BUFS 8 +#define UDSL_MAX_RCV_BUF_SIZE 1024 /* ATM cells */ +#define UDSL_MAX_SND_BUF_SIZE 1024 /* ATM cells */ +#define UDSL_DEFAULT_RCV_URBS 2 +#define UDSL_DEFAULT_SND_URBS 2 +#define UDSL_DEFAULT_RCV_BUFS 4 +#define UDSL_DEFAULT_SND_BUFS 4 +#define UDSL_DEFAULT_RCV_BUF_SIZE 64 /* ATM cells */ +#define UDSL_DEFAULT_SND_BUF_SIZE 64 /* ATM cells */ + +#define ATM_CELL_HEADER (ATM_CELL_SIZE - ATM_CELL_PAYLOAD) +#define UDSL_NUM_CELLS(x) (((x) + ATM_AAL5_TRAILER + ATM_CELL_PAYLOAD - 1) / ATM_CELL_PAYLOAD) + +/* receive */ + +struct udsl_receive_buffer { + struct list_head list; + unsigned char *base; + unsigned int filled_cells; +}; + +struct udsl_receiver { + struct list_head list; + struct udsl_receive_buffer *buffer; + struct urb *urb; + struct udsl_instance_data *instance; +}; + +struct udsl_vcc_data { + /* vpi/vci lookup */ + struct list_head list; + short vpi; + int vci; + struct atm_vcc *vcc; + + /* raw cell reassembly */ + struct sk_buff *sarb; +}; + +/* send */ + +struct udsl_send_buffer { + struct list_head list; + unsigned char *base; + unsigned char *free_start; + unsigned int free_cells; +}; + +struct udsl_sender { + struct list_head list; + struct udsl_send_buffer *buffer; + struct urb *urb; + struct udsl_instance_data *instance; +}; + +struct udsl_control { + struct atm_skb_data atm_data; + unsigned int num_cells; + unsigned int num_entire; + unsigned int pdu_padding; + unsigned char cell_header [ATM_CELL_HEADER]; + unsigned char aal5_trailer [ATM_AAL5_TRAILER]; +}; + +#define UDSL_SKB(x) ((struct udsl_control *)(x)->cb) + +/* main driver data */ + +enum udsl_status { + UDSL_NO_FIRMWARE, + UDSL_LOADING_FIRMWARE, + UDSL_LOADED_FIRMWARE +}; + +struct udsl_instance_data { + struct kref refcount; + struct semaphore serialize; + + /* USB device part */ + struct usb_device *usb_dev; + char description [64]; + int data_endpoint; + int snd_padding; + int rcv_padding; + const char *driver_name; + + /* ATM device part */ + struct atm_dev *atm_dev; + struct list_head vcc_list; + + /* firmware */ + int (*firmware_wait) (struct udsl_instance_data *); + enum udsl_status status; + wait_queue_head_t firmware_waiters; + + /* receive */ + struct udsl_receiver receivers [UDSL_MAX_RCV_URBS]; + struct udsl_receive_buffer receive_buffers [UDSL_MAX_RCV_BUFS]; + + spinlock_t receive_lock; + struct list_head spare_receivers; + struct list_head filled_receive_buffers; + + struct tasklet_struct receive_tasklet; + struct list_head spare_receive_buffers; + + /* send */ + struct udsl_sender senders [UDSL_MAX_SND_URBS]; + struct udsl_send_buffer send_buffers [UDSL_MAX_SND_BUFS]; + + struct sk_buff_head sndqueue; + + spinlock_t send_lock; + struct list_head spare_senders; + struct list_head spare_send_buffers; + + struct tasklet_struct send_tasklet; + struct sk_buff *current_skb; /* being emptied */ + struct udsl_send_buffer *current_buffer; /* being filled */ + struct list_head filled_send_buffers; +}; + +extern int udsl_instance_setup(struct usb_device *dev, struct udsl_instance_data *instance); +extern void udsl_instance_disconnect (struct udsl_instance_data *instance); +extern void udsl_get_instance(struct udsl_instance_data *instance); +extern void udsl_put_instance(struct udsl_instance_data *instance); diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 8c958af4a1d3..d6401c0a80d5 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -121,18 +121,6 @@ config USB_CYTHERM To compile this driver as a module, choose M here: the module will be called cytherm. -config USB_SPEEDTOUCH - tristate "Alcatel Speedtouch USB support" - depends on USB && ATM - select CRC32 - help - Say Y here if you have an Alcatel SpeedTouch USB or SpeedTouch 330 - modem. In order to use your modem you will need to install some user - space tools, see for details. - - To compile this driver as a module, choose M here: the - module will be called speedtch. - config USB_PHIDGETSERVO tristate "USB PhidgetServo support" depends on USB diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index d641a470e8d7..074a1d66250b 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -11,7 +11,6 @@ obj-$(CONFIG_USB_LCD) += usblcd.o obj-$(CONFIG_USB_LED) += usbled.o obj-$(CONFIG_USB_LEGOTOWER) += legousbtower.o obj-$(CONFIG_USB_RIO500) += rio500.o -obj-$(CONFIG_USB_SPEEDTOUCH) += speedtch.o obj-$(CONFIG_USB_TEST) += usbtest.o obj-$(CONFIG_USB_TIGL) += tiglusb.o obj-$(CONFIG_USB_USS720) += uss720.o diff --git a/drivers/usb/misc/speedtch.c b/drivers/usb/misc/speedtch.c deleted file mode 100644 index 667e2d1b227c..000000000000 --- a/drivers/usb/misc/speedtch.c +++ /dev/null @@ -1,1373 +0,0 @@ -/****************************************************************************** - * speedtouch.c - Alcatel SpeedTouch USB xDSL modem driver - * - * Copyright (C) 2001, Alcatel - * Copyright (C) 2003, Duncan Sands - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License along with - * this program; if not, write to the Free Software Foundation, Inc., 59 - * Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - ******************************************************************************/ - -/* - * Written by Johan Verrept, maintained by Duncan Sands (duncan.sands@free.fr) - * - * 1.7+: - See the check-in logs - * - * 1.6: - No longer opens a connection if the firmware is not loaded - * - Added support for the speedtouch 330 - * - Removed the limit on the number of devices - * - Module now autoloads on device plugin - * - Merged relevant parts of sarlib - * - Replaced the kernel thread with a tasklet - * - New packet transmission code - * - Changed proc file contents - * - Fixed all known SMP races - * - Many fixes and cleanups - * - Various fixes by Oliver Neukum (oliver@neukum.name) - * - * 1.5A: - Version for inclusion in 2.5 series kernel - * - Modifications by Richard Purdie (rpurdie@rpsys.net) - * - made compatible with kernel 2.5.6 onwards by changing - * udsl_usb_send_data_context->urb to a pointer and adding code - * to alloc and free it - * - remove_wait_queue() added to udsl_atm_processqueue_thread() - * - * 1.5: - fixed memory leak when atmsar_decode_aal5 returned NULL. - * (reported by stephen.robinson@zen.co.uk) - * - * 1.4: - changed the spin_lock() under interrupt to spin_lock_irqsave() - * - unlink all active send urbs of a vcc that is being closed. - * - * 1.3.1: - added the version number - * - * 1.3: - Added multiple send urb support - * - fixed memory leak and vcc->tx_inuse starvation bug - * when not enough memory left in vcc. - * - * 1.2: - Fixed race condition in udsl_usb_send_data() - * 1.1: - Turned off packet debugging - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* -#define DEBUG -#define VERBOSE_DEBUG -*/ - -#if !defined (DEBUG) && defined (CONFIG_USB_DEBUG) -# define DEBUG -#endif - -#include - -#ifdef DEBUG -#define DEBUG_ON(x) BUG_ON(x) -#else -#define DEBUG_ON(x) do { if (x); } while (0) -#endif - -#ifdef VERBOSE_DEBUG -static int udsl_print_packet (const unsigned char *data, int len); -#define PACKETDEBUG(arg...) udsl_print_packet (arg) -#define vdbg(arg...) dbg (arg) -#else -#define PACKETDEBUG(arg...) -#define vdbg(arg...) -#endif - -#define DRIVER_AUTHOR "Johan Verrept, Duncan Sands " -#define DRIVER_VERSION "1.8" -#define DRIVER_DESC "Alcatel SpeedTouch USB driver version " DRIVER_VERSION - -static const char udsl_driver_name [] = "speedtch"; - -#define SPEEDTOUCH_VENDORID 0x06b9 -#define SPEEDTOUCH_PRODUCTID 0x4061 - -#define UDSL_MAX_RCV_URBS 4 -#define UDSL_MAX_SND_URBS 4 -#define UDSL_MAX_RCV_BUFS 8 -#define UDSL_MAX_SND_BUFS 8 -#define UDSL_MAX_RCV_BUF_SIZE 1024 /* ATM cells */ -#define UDSL_MAX_SND_BUF_SIZE 1024 /* ATM cells */ -#define UDSL_DEFAULT_RCV_URBS 2 -#define UDSL_DEFAULT_SND_URBS 2 -#define UDSL_DEFAULT_RCV_BUFS 4 -#define UDSL_DEFAULT_SND_BUFS 4 -#define UDSL_DEFAULT_RCV_BUF_SIZE 64 /* ATM cells */ -#define UDSL_DEFAULT_SND_BUF_SIZE 64 /* ATM cells */ - -static unsigned int num_rcv_urbs = UDSL_DEFAULT_RCV_URBS; -static unsigned int num_snd_urbs = UDSL_DEFAULT_SND_URBS; -static unsigned int num_rcv_bufs = UDSL_DEFAULT_RCV_BUFS; -static unsigned int num_snd_bufs = UDSL_DEFAULT_SND_BUFS; -static unsigned int rcv_buf_size = UDSL_DEFAULT_RCV_BUF_SIZE; -static unsigned int snd_buf_size = UDSL_DEFAULT_SND_BUF_SIZE; - -module_param (num_rcv_urbs, uint, 0444); -MODULE_PARM_DESC (num_rcv_urbs, "Number of urbs used for reception (range: 0-" __MODULE_STRING (UDSL_MAX_RCV_URBS) ", default: " __MODULE_STRING (UDSL_DEFAULT_RCV_URBS) ")"); - -module_param (num_snd_urbs, uint, 0444); -MODULE_PARM_DESC (num_snd_urbs, "Number of urbs used for transmission (range: 0-" __MODULE_STRING (UDSL_MAX_SND_URBS) ", default: " __MODULE_STRING (UDSL_DEFAULT_SND_URBS) ")"); - -module_param (num_rcv_bufs, uint, 0444); -MODULE_PARM_DESC (num_rcv_bufs, "Number of buffers used for reception (range: 0-" __MODULE_STRING (UDSL_MAX_RCV_BUFS) ", default: " __MODULE_STRING (UDSL_DEFAULT_RCV_BUFS) ")"); - -module_param (num_snd_bufs, uint, 0444); -MODULE_PARM_DESC (num_snd_bufs, "Number of buffers used for transmission (range: 0-" __MODULE_STRING (UDSL_MAX_SND_BUFS) ", default: " __MODULE_STRING (UDSL_DEFAULT_SND_BUFS) ")"); - -module_param (rcv_buf_size, uint, 0444); -MODULE_PARM_DESC (rcv_buf_size, "Size of the buffers used for reception (range: 0-" __MODULE_STRING (UDSL_MAX_RCV_BUF_SIZE) ", default: " __MODULE_STRING (UDSL_DEFAULT_RCV_BUF_SIZE) ")"); - -module_param (snd_buf_size, uint, 0444); -MODULE_PARM_DESC (snd_buf_size, "Size of the buffers used for transmission (range: 0-" __MODULE_STRING (UDSL_MAX_SND_BUF_SIZE) ", default: " __MODULE_STRING (UDSL_DEFAULT_SND_BUF_SIZE) ")"); - -#define UDSL_IOCTL_LINE_UP 1 -#define UDSL_IOCTL_LINE_DOWN 2 - -#define UDSL_ENDPOINT_DATA_OUT 0x07 -#define UDSL_ENDPOINT_DATA_IN 0x87 - -#define ATM_CELL_HEADER (ATM_CELL_SIZE - ATM_CELL_PAYLOAD) -#define UDSL_NUM_CELLS(x) (((x) + ATM_AAL5_TRAILER + ATM_CELL_PAYLOAD - 1) / ATM_CELL_PAYLOAD) - -#define hex2int(c) ( (c >= '0') && (c <= '9') ? (c - '0') : ((c & 0xf) + 9) ) - -static struct usb_device_id udsl_usb_ids [] = { - { USB_DEVICE (SPEEDTOUCH_VENDORID, SPEEDTOUCH_PRODUCTID) }, - { } -}; - -MODULE_DEVICE_TABLE (usb, udsl_usb_ids); - -/* receive */ - -struct udsl_receive_buffer { - struct list_head list; - unsigned char *base; - unsigned int filled_cells; -}; - -struct udsl_receiver { - struct list_head list; - struct udsl_receive_buffer *buffer; - struct urb *urb; - struct udsl_instance_data *instance; -}; - -struct udsl_vcc_data { - /* vpi/vci lookup */ - struct list_head list; - short vpi; - int vci; - struct atm_vcc *vcc; - - /* raw cell reassembly */ - struct sk_buff *sarb; -}; - -/* send */ - -struct udsl_send_buffer { - struct list_head list; - unsigned char *base; - unsigned char *free_start; - unsigned int free_cells; -}; - -struct udsl_sender { - struct list_head list; - struct udsl_send_buffer *buffer; - struct urb *urb; - struct udsl_instance_data *instance; -}; - -struct udsl_control { - struct atm_skb_data atm_data; - unsigned int num_cells; - unsigned int num_entire; - unsigned int pdu_padding; - unsigned char cell_header [ATM_CELL_HEADER]; - unsigned char aal5_trailer [ATM_AAL5_TRAILER]; -}; - -#define UDSL_SKB(x) ((struct udsl_control *)(x)->cb) - -/* main driver data */ - -struct udsl_instance_data { - struct semaphore serialize; - - /* USB device part */ - struct usb_device *usb_dev; - char description [64]; - int firmware_loaded; - - /* ATM device part */ - struct atm_dev *atm_dev; - struct list_head vcc_list; - - /* receive */ - struct udsl_receiver receivers [UDSL_MAX_RCV_URBS]; - struct udsl_receive_buffer receive_buffers [UDSL_MAX_RCV_BUFS]; - - spinlock_t receive_lock; - struct list_head spare_receivers; - struct list_head filled_receive_buffers; - - struct tasklet_struct receive_tasklet; - struct list_head spare_receive_buffers; - - /* send */ - struct udsl_sender senders [UDSL_MAX_SND_URBS]; - struct udsl_send_buffer send_buffers [UDSL_MAX_SND_BUFS]; - - struct sk_buff_head sndqueue; - - spinlock_t send_lock; - struct list_head spare_senders; - struct list_head spare_send_buffers; - - struct tasklet_struct send_tasklet; - struct sk_buff *current_skb; /* being emptied */ - struct udsl_send_buffer *current_buffer; /* being filled */ - struct list_head filled_send_buffers; -}; - -/* ATM */ - -static void udsl_atm_dev_close (struct atm_dev *dev); -static int udsl_atm_open (struct atm_vcc *vcc); -static void udsl_atm_close (struct atm_vcc *vcc); -static int udsl_atm_ioctl (struct atm_dev *dev, unsigned int cmd, void __user *arg); -static int udsl_atm_send (struct atm_vcc *vcc, struct sk_buff *skb); -static int udsl_atm_proc_read (struct atm_dev *atm_dev, loff_t *pos, char *page); - -static struct atmdev_ops udsl_atm_devops = { - .dev_close = udsl_atm_dev_close, - .open = udsl_atm_open, - .close = udsl_atm_close, - .ioctl = udsl_atm_ioctl, - .send = udsl_atm_send, - .proc_read = udsl_atm_proc_read, - .owner = THIS_MODULE, -}; - -/* USB */ - -static int udsl_usb_probe (struct usb_interface *intf, const struct usb_device_id *id); -static void udsl_usb_disconnect (struct usb_interface *intf); -static int udsl_usb_ioctl (struct usb_interface *intf, unsigned int code, void *user_data); - -static struct usb_driver udsl_usb_driver = { - .owner = THIS_MODULE, - .name = udsl_driver_name, - .probe = udsl_usb_probe, - .disconnect = udsl_usb_disconnect, - .ioctl = udsl_usb_ioctl, - .id_table = udsl_usb_ids, -}; - - -/*********** -** misc ** -***********/ - -static inline void udsl_pop (struct atm_vcc *vcc, struct sk_buff *skb) -{ - if (vcc->pop) - vcc->pop (vcc, skb); - else - dev_kfree_skb (skb); -} - - -/************* -** decode ** -*************/ - -static inline struct udsl_vcc_data *udsl_find_vcc (struct udsl_instance_data *instance, short vpi, int vci) -{ - struct udsl_vcc_data *vcc; - - list_for_each_entry (vcc, &instance->vcc_list, list) - if ((vcc->vci == vci) && (vcc->vpi == vpi)) - return vcc; - return NULL; -} - -static void udsl_extract_cells (struct udsl_instance_data *instance, unsigned char *source, unsigned int howmany) -{ - struct udsl_vcc_data *cached_vcc = NULL; - struct atm_vcc *vcc; - struct sk_buff *sarb; - struct udsl_vcc_data *vcc_data; - int cached_vci = 0; - unsigned int i; - int pti; - int vci; - short cached_vpi = 0; - short vpi; - - for (i = 0; i < howmany; i++, source += ATM_CELL_SIZE) { - vpi = ((source [0] & 0x0f) << 4) | (source [1] >> 4); - vci = ((source [1] & 0x0f) << 12) | (source [2] << 4) | (source [3] >> 4); - pti = (source [3] & 0x2) != 0; - - vdbg ("udsl_extract_cells: vpi %hd, vci %d, pti %d", vpi, vci, pti); - - if (cached_vcc && (vci == cached_vci) && (vpi == cached_vpi)) - vcc_data = cached_vcc; - else if ((vcc_data = udsl_find_vcc (instance, vpi, vci))) { - cached_vcc = vcc_data; - cached_vpi = vpi; - cached_vci = vci; - } else { - dbg ("udsl_extract_cells: unknown vpi/vci (%hd/%d)!", vpi, vci); - continue; - } - - vcc = vcc_data->vcc; - sarb = vcc_data->sarb; - - if (sarb->tail + ATM_CELL_PAYLOAD > sarb->end) { - dbg ("udsl_extract_cells: buffer overrun (sarb->len %u, vcc: 0x%p)!", sarb->len, vcc); - /* discard cells already received */ - skb_trim (sarb, 0); - } - - memcpy (sarb->tail, source + ATM_CELL_HEADER, ATM_CELL_PAYLOAD); - __skb_put (sarb, ATM_CELL_PAYLOAD); - - if (pti) { - struct sk_buff *skb; - unsigned int length; - unsigned int pdu_length; - - length = (source [ATM_CELL_SIZE - 6] << 8) + source [ATM_CELL_SIZE - 5]; - - /* guard against overflow */ - if (length > ATM_MAX_AAL5_PDU) { - dbg ("udsl_extract_cells: bogus length %u (vcc: 0x%p)!", length, vcc); - atomic_inc (&vcc->stats->rx_err); - goto out; - } - - pdu_length = UDSL_NUM_CELLS (length) * ATM_CELL_PAYLOAD; - - if (sarb->len < pdu_length) { - dbg ("udsl_extract_cells: bogus pdu_length %u (sarb->len: %u, vcc: 0x%p)!", pdu_length, sarb->len, vcc); - atomic_inc (&vcc->stats->rx_err); - goto out; - } - - if (crc32_be (~0, sarb->tail - pdu_length, pdu_length) != 0xc704dd7b) { - dbg ("udsl_extract_cells: packet failed crc check (vcc: 0x%p)!", vcc); - atomic_inc (&vcc->stats->rx_err); - goto out; - } - - vdbg ("udsl_extract_cells: got packet (length: %u, pdu_length: %u, vcc: 0x%p)", length, pdu_length, vcc); - - if (!(skb = dev_alloc_skb (length))) { - dbg ("udsl_extract_cells: no memory for skb (length: %u)!", length); - atomic_inc (&vcc->stats->rx_drop); - goto out; - } - - vdbg ("udsl_extract_cells: allocated new sk_buff (skb: 0x%p, skb->truesize: %u)", skb, skb->truesize); - - if (!atm_charge (vcc, skb->truesize)) { - dbg ("udsl_extract_cells: failed atm_charge (skb->truesize: %u)!", skb->truesize); - dev_kfree_skb (skb); - goto out; /* atm_charge increments rx_drop */ - } - - memcpy (skb->data, sarb->tail - pdu_length, length); - __skb_put (skb, length); - - vdbg ("udsl_extract_cells: sending skb 0x%p, skb->len %u, skb->truesize %u", skb, skb->len, skb->truesize); - - PACKETDEBUG (skb->data, skb->len); - - vcc->push (vcc, skb); - - atomic_inc (&vcc->stats->rx); -out: - skb_trim (sarb, 0); - } - } -} - - -/************* -** encode ** -*************/ - -static const unsigned char zeros [ATM_CELL_PAYLOAD]; - -static void udsl_groom_skb (struct atm_vcc *vcc, struct sk_buff *skb) -{ - struct udsl_control *ctrl = UDSL_SKB (skb); - unsigned int zero_padding; - u32 crc; - - ctrl->atm_data.vcc = vcc; - ctrl->cell_header [0] = vcc->vpi >> 4; - ctrl->cell_header [1] = (vcc->vpi << 4) | (vcc->vci >> 12); - ctrl->cell_header [2] = vcc->vci >> 4; - ctrl->cell_header [3] = vcc->vci << 4; - ctrl->cell_header [4] = 0xec; - - ctrl->num_cells = UDSL_NUM_CELLS (skb->len); - ctrl->num_entire = skb->len / ATM_CELL_PAYLOAD; - - zero_padding = ctrl->num_cells * ATM_CELL_PAYLOAD - skb->len - ATM_AAL5_TRAILER; - - if (ctrl->num_entire + 1 < ctrl->num_cells) - ctrl->pdu_padding = zero_padding - (ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER); - else - ctrl->pdu_padding = zero_padding; - - ctrl->aal5_trailer [0] = 0; /* UU = 0 */ - ctrl->aal5_trailer [1] = 0; /* CPI = 0 */ - ctrl->aal5_trailer [2] = skb->len >> 8; - ctrl->aal5_trailer [3] = skb->len; - - crc = crc32_be (~0, skb->data, skb->len); - crc = crc32_be (crc, zeros, zero_padding); - crc = crc32_be (crc, ctrl->aal5_trailer, 4); - crc = ~crc; - - ctrl->aal5_trailer [4] = crc >> 24; - ctrl->aal5_trailer [5] = crc >> 16; - ctrl->aal5_trailer [6] = crc >> 8; - ctrl->aal5_trailer [7] = crc; -} - -static unsigned int udsl_write_cells (unsigned int howmany, struct sk_buff *skb, unsigned char **target_p) -{ - struct udsl_control *ctrl = UDSL_SKB (skb); - unsigned char *target = *target_p; - unsigned int nc, ne, i; - - vdbg ("udsl_write_cells: howmany=%u, skb->len=%d, num_cells=%u, num_entire=%u, pdu_padding=%u", howmany, skb->len, ctrl->num_cells, ctrl->num_entire, ctrl->pdu_padding); - - nc = ctrl->num_cells; - ne = min (howmany, ctrl->num_entire); - - for (i = 0; i < ne; i++) { - memcpy (target, ctrl->cell_header, ATM_CELL_HEADER); - target += ATM_CELL_HEADER; - memcpy (target, skb->data, ATM_CELL_PAYLOAD); - target += ATM_CELL_PAYLOAD; - __skb_pull (skb, ATM_CELL_PAYLOAD); - } - - ctrl->num_entire -= ne; - - if (!(ctrl->num_cells -= ne) || !(howmany -= ne)) - goto out; - - memcpy (target, ctrl->cell_header, ATM_CELL_HEADER); - target += ATM_CELL_HEADER; - memcpy (target, skb->data, skb->len); - target += skb->len; - __skb_pull (skb, skb->len); - memset (target, 0, ctrl->pdu_padding); - target += ctrl->pdu_padding; - - if (--ctrl->num_cells) { - if (!--howmany) { - ctrl->pdu_padding = ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER; - goto out; - } - - memcpy (target, ctrl->cell_header, ATM_CELL_HEADER); - target += ATM_CELL_HEADER; - memset (target, 0, ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER); - target += ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER; - - DEBUG_ON (--ctrl->num_cells); - } - - memcpy (target, ctrl->aal5_trailer, ATM_AAL5_TRAILER); - target += ATM_AAL5_TRAILER; - /* set pti bit in last cell */ - *(target + 3 - ATM_CELL_SIZE) |= 0x2; - -out: - *target_p = target; - return nc - ctrl->num_cells; -} - - -/************** -** receive ** -**************/ - -static void udsl_complete_receive (struct urb *urb, struct pt_regs *regs) -{ - struct udsl_receive_buffer *buf; - struct udsl_instance_data *instance; - struct udsl_receiver *rcv; - unsigned long flags; - - if (!urb || !(rcv = urb->context)) { - dbg ("udsl_complete_receive: bad urb!"); - return; - } - - instance = rcv->instance; - buf = rcv->buffer; - - buf->filled_cells = urb->actual_length / ATM_CELL_SIZE; - - vdbg ("udsl_complete_receive: urb 0x%p, status %d, actual_length %d, filled_cells %u, rcv 0x%p, buf 0x%p", urb, urb->status, urb->actual_length, buf->filled_cells, rcv, buf); - - DEBUG_ON (buf->filled_cells > rcv_buf_size); - - /* may not be in_interrupt() */ - spin_lock_irqsave (&instance->receive_lock, flags); - list_add (&rcv->list, &instance->spare_receivers); - list_add_tail (&buf->list, &instance->filled_receive_buffers); - if (likely (!urb->status)) - tasklet_schedule (&instance->receive_tasklet); - spin_unlock_irqrestore (&instance->receive_lock, flags); -} - -static void udsl_process_receive (unsigned long data) -{ - struct udsl_receive_buffer *buf; - struct udsl_instance_data *instance = (struct udsl_instance_data *) data; - struct udsl_receiver *rcv; - int err; - -made_progress: - while (!list_empty (&instance->spare_receive_buffers)) { - spin_lock_irq (&instance->receive_lock); - if (list_empty (&instance->spare_receivers)) { - spin_unlock_irq (&instance->receive_lock); - break; - } - rcv = list_entry (instance->spare_receivers.next, struct udsl_receiver, list); - list_del (&rcv->list); - spin_unlock_irq (&instance->receive_lock); - - buf = list_entry (instance->spare_receive_buffers.next, struct udsl_receive_buffer, list); - list_del (&buf->list); - - rcv->buffer = buf; - - usb_fill_bulk_urb (rcv->urb, - instance->usb_dev, - usb_rcvbulkpipe (instance->usb_dev, UDSL_ENDPOINT_DATA_IN), - buf->base, - rcv_buf_size * ATM_CELL_SIZE, - udsl_complete_receive, - rcv); - - vdbg ("udsl_process_receive: sending urb 0x%p, rcv 0x%p, buf 0x%p", rcv->urb, rcv, buf); - - if ((err = usb_submit_urb(rcv->urb, GFP_ATOMIC)) < 0) { - dbg ("udsl_process_receive: urb submission failed (%d)!", err); - list_add (&buf->list, &instance->spare_receive_buffers); - spin_lock_irq (&instance->receive_lock); - list_add (&rcv->list, &instance->spare_receivers); - spin_unlock_irq (&instance->receive_lock); - break; - } - } - - spin_lock_irq (&instance->receive_lock); - if (list_empty (&instance->filled_receive_buffers)) { - spin_unlock_irq (&instance->receive_lock); - return; /* done - no more buffers */ - } - buf = list_entry (instance->filled_receive_buffers.next, struct udsl_receive_buffer, list); - list_del (&buf->list); - spin_unlock_irq (&instance->receive_lock); - vdbg ("udsl_process_receive: processing buf 0x%p", buf); - udsl_extract_cells (instance, buf->base, buf->filled_cells); - list_add (&buf->list, &instance->spare_receive_buffers); - goto made_progress; -} - - -/*********** -** send ** -***********/ - -static void udsl_complete_send (struct urb *urb, struct pt_regs *regs) -{ - struct udsl_instance_data *instance; - struct udsl_sender *snd; - unsigned long flags; - - if (!urb || !(snd = urb->context) || !(instance = snd->instance)) { - dbg ("udsl_complete_send: bad urb!"); - return; - } - - vdbg ("udsl_complete_send: urb 0x%p, status %d, snd 0x%p, buf 0x%p", urb, urb->status, snd, snd->buffer); - - /* may not be in_interrupt() */ - spin_lock_irqsave (&instance->send_lock, flags); - list_add (&snd->list, &instance->spare_senders); - list_add (&snd->buffer->list, &instance->spare_send_buffers); - tasklet_schedule (&instance->send_tasklet); - spin_unlock_irqrestore (&instance->send_lock, flags); -} - -static void udsl_process_send (unsigned long data) -{ - struct udsl_send_buffer *buf; - struct udsl_instance_data *instance = (struct udsl_instance_data *) data; - struct sk_buff *skb; - struct udsl_sender *snd; - int err; - unsigned int num_written; - -made_progress: - spin_lock_irq (&instance->send_lock); - while (!list_empty (&instance->spare_senders)) { - if (!list_empty (&instance->filled_send_buffers)) { - buf = list_entry (instance->filled_send_buffers.next, struct udsl_send_buffer, list); - list_del (&buf->list); - } else if ((buf = instance->current_buffer)) { - instance->current_buffer = NULL; - } else /* all buffers empty */ - break; - - snd = list_entry (instance->spare_senders.next, struct udsl_sender, list); - list_del (&snd->list); - spin_unlock_irq (&instance->send_lock); - - snd->buffer = buf; - usb_fill_bulk_urb (snd->urb, - instance->usb_dev, - usb_sndbulkpipe (instance->usb_dev, UDSL_ENDPOINT_DATA_OUT), - buf->base, - (snd_buf_size - buf->free_cells) * ATM_CELL_SIZE, - udsl_complete_send, - snd); - - vdbg ("udsl_process_send: submitting urb 0x%p (%d cells), snd 0x%p, buf 0x%p", snd->urb, snd_buf_size - buf->free_cells, snd, buf); - - if ((err = usb_submit_urb(snd->urb, GFP_ATOMIC)) < 0) { - dbg ("udsl_process_send: urb submission failed (%d)!", err); - spin_lock_irq (&instance->send_lock); - list_add (&snd->list, &instance->spare_senders); - spin_unlock_irq (&instance->send_lock); - list_add (&buf->list, &instance->filled_send_buffers); - return; /* bail out */ - } - - spin_lock_irq (&instance->send_lock); - } /* while */ - spin_unlock_irq (&instance->send_lock); - - if (!instance->current_skb && !(instance->current_skb = skb_dequeue (&instance->sndqueue))) - return; /* done - no more skbs */ - - skb = instance->current_skb; - - if (!(buf = instance->current_buffer)) { - spin_lock_irq (&instance->send_lock); - if (list_empty (&instance->spare_send_buffers)) { - instance->current_buffer = NULL; - spin_unlock_irq (&instance->send_lock); - return; /* done - no more buffers */ - } - buf = list_entry (instance->spare_send_buffers.next, struct udsl_send_buffer, list); - list_del (&buf->list); - spin_unlock_irq (&instance->send_lock); - - buf->free_start = buf->base; - buf->free_cells = snd_buf_size; - - instance->current_buffer = buf; - } - - num_written = udsl_write_cells (buf->free_cells, skb, &buf->free_start); - - vdbg ("udsl_process_send: wrote %u cells from skb 0x%p to buffer 0x%p", num_written, skb, buf); - - if (!(buf->free_cells -= num_written)) { - list_add_tail (&buf->list, &instance->filled_send_buffers); - instance->current_buffer = NULL; - } - - vdbg ("udsl_process_send: buffer contains %d cells, %d left", snd_buf_size - buf->free_cells, buf->free_cells); - - if (!UDSL_SKB (skb)->num_cells) { - struct atm_vcc *vcc = UDSL_SKB (skb)->atm_data.vcc; - - udsl_pop (vcc, skb); - instance->current_skb = NULL; - - atomic_inc (&vcc->stats->tx); - } - - goto made_progress; -} - -static void udsl_cancel_send (struct udsl_instance_data *instance, struct atm_vcc *vcc) -{ - struct sk_buff *skb, *n; - - dbg ("udsl_cancel_send entered"); - spin_lock_irq (&instance->sndqueue.lock); - for (skb = instance->sndqueue.next, n = skb->next; skb != (struct sk_buff *)&instance->sndqueue; skb = n, n = skb->next) - if (UDSL_SKB (skb)->atm_data.vcc == vcc) { - dbg ("udsl_cancel_send: popping skb 0x%p", skb); - __skb_unlink (skb, &instance->sndqueue); - udsl_pop (vcc, skb); - } - spin_unlock_irq (&instance->sndqueue.lock); - - tasklet_disable (&instance->send_tasklet); - if ((skb = instance->current_skb) && (UDSL_SKB (skb)->atm_data.vcc == vcc)) { - dbg ("udsl_cancel_send: popping current skb (0x%p)", skb); - instance->current_skb = NULL; - udsl_pop (vcc, skb); - } - tasklet_enable (&instance->send_tasklet); - dbg ("udsl_cancel_send done"); -} - -static int udsl_atm_send (struct atm_vcc *vcc, struct sk_buff *skb) -{ - struct udsl_instance_data *instance = vcc->dev->dev_data; - int err; - - vdbg ("udsl_atm_send called (skb 0x%p, len %u)", skb, skb->len); - - if (!instance || !instance->usb_dev) { - dbg ("udsl_atm_send: NULL data!"); - err = -ENODEV; - goto fail; - } - - if (vcc->qos.aal != ATM_AAL5) { - dbg ("udsl_atm_send: unsupported ATM type %d!", vcc->qos.aal); - err = -EINVAL; - goto fail; - } - - if (skb->len > ATM_MAX_AAL5_PDU) { - dbg ("udsl_atm_send: packet too long (%d vs %d)!", skb->len, ATM_MAX_AAL5_PDU); - err = -EINVAL; - goto fail; - } - - PACKETDEBUG (skb->data, skb->len); - - udsl_groom_skb (vcc, skb); - skb_queue_tail (&instance->sndqueue, skb); - tasklet_schedule (&instance->send_tasklet); - - return 0; - -fail: - udsl_pop (vcc, skb); - return err; -} - - -/********** -** ATM ** -**********/ - -static void udsl_atm_dev_close (struct atm_dev *dev) -{ - struct udsl_instance_data *instance = dev->dev_data; - - if (!instance) { - dbg ("udsl_atm_dev_close: NULL instance!"); - return; - } - - dbg ("udsl_atm_dev_close: queue has %u elements", instance->sndqueue.qlen); - - tasklet_kill (&instance->receive_tasklet); - tasklet_kill (&instance->send_tasklet); - kfree (instance); - dev->dev_data = NULL; -} - -static int udsl_atm_proc_read (struct atm_dev *atm_dev, loff_t *pos, char *page) -{ - struct udsl_instance_data *instance = atm_dev->dev_data; - int left = *pos; - - if (!instance) { - dbg ("udsl_atm_proc_read: NULL instance!"); - return -ENODEV; - } - - if (!left--) - return sprintf (page, "%s\n", instance->description); - - if (!left--) - return sprintf (page, "MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", - atm_dev->esi [0], atm_dev->esi [1], atm_dev->esi [2], - atm_dev->esi [3], atm_dev->esi [4], atm_dev->esi [5]); - - if (!left--) - return sprintf (page, "AAL5: tx %d ( %d err ), rx %d ( %d err, %d drop )\n", - atomic_read (&atm_dev->stats.aal5.tx), - atomic_read (&atm_dev->stats.aal5.tx_err), - atomic_read (&atm_dev->stats.aal5.rx), - atomic_read (&atm_dev->stats.aal5.rx_err), - atomic_read (&atm_dev->stats.aal5.rx_drop)); - - if (!left--) { - switch (atm_dev->signal) { - case ATM_PHY_SIG_FOUND: - sprintf (page, "Line up"); - break; - case ATM_PHY_SIG_LOST: - sprintf (page, "Line down"); - break; - default: - sprintf (page, "Line state unknown"); - break; - } - - if (instance->usb_dev) { - if (!instance->firmware_loaded) - strcat (page, ", no firmware\n"); - else - strcat (page, ", firmware loaded\n"); - } else - strcat (page, ", disconnected\n"); - - return strlen (page); - } - - return 0; -} - -static int udsl_atm_open (struct atm_vcc *vcc) -{ - struct udsl_instance_data *instance = vcc->dev->dev_data; - struct udsl_vcc_data *new; - unsigned int max_pdu; - int vci = vcc->vci; - short vpi = vcc->vpi; - - dbg ("udsl_atm_open: vpi %hd, vci %d", vpi, vci); - - if (!instance || !instance->usb_dev) { - dbg ("udsl_atm_open: NULL data!"); - return -ENODEV; - } - - /* only support AAL5 */ - if ((vcc->qos.aal != ATM_AAL5) || (vcc->qos.rxtp.max_sdu < 0) || (vcc->qos.rxtp.max_sdu > ATM_MAX_AAL5_PDU)) { - dbg ("udsl_atm_open: unsupported ATM type %d!", vcc->qos.aal); - return -EINVAL; - } - - if (!instance->firmware_loaded) { - dbg ("udsl_atm_open: firmware not loaded!"); - return -EAGAIN; - } - - down (&instance->serialize); /* vs self, udsl_atm_close */ - - if (udsl_find_vcc (instance, vpi, vci)) { - dbg ("udsl_atm_open: %hd/%d already in use!", vpi, vci); - up (&instance->serialize); - return -EADDRINUSE; - } - - if (!(new = kmalloc (sizeof (struct udsl_vcc_data), GFP_KERNEL))) { - dbg ("udsl_atm_open: no memory for vcc_data!"); - up (&instance->serialize); - return -ENOMEM; - } - - memset (new, 0, sizeof (struct udsl_vcc_data)); - new->vcc = vcc; - new->vpi = vpi; - new->vci = vci; - - /* udsl_extract_cells requires at least one cell */ - max_pdu = max (1, UDSL_NUM_CELLS (vcc->qos.rxtp.max_sdu)) * ATM_CELL_PAYLOAD; - if (!(new->sarb = alloc_skb (max_pdu, GFP_KERNEL))) { - dbg ("udsl_atm_open: no memory for SAR buffer!"); - kfree (new); - up (&instance->serialize); - return -ENOMEM; - } - - vcc->dev_data = new; - - tasklet_disable (&instance->receive_tasklet); - list_add (&new->list, &instance->vcc_list); - tasklet_enable (&instance->receive_tasklet); - - set_bit (ATM_VF_ADDR, &vcc->flags); - set_bit (ATM_VF_PARTIAL, &vcc->flags); - set_bit (ATM_VF_READY, &vcc->flags); - - up (&instance->serialize); - - tasklet_schedule (&instance->receive_tasklet); - - dbg ("udsl_atm_open: allocated vcc data 0x%p (max_pdu: %u)", new, max_pdu); - - return 0; -} - -static void udsl_atm_close (struct atm_vcc *vcc) -{ - struct udsl_instance_data *instance = vcc->dev->dev_data; - struct udsl_vcc_data *vcc_data = vcc->dev_data; - - dbg ("udsl_atm_close called"); - - if (!instance || !vcc_data) { - dbg ("udsl_atm_close: NULL data!"); - return; - } - - dbg ("udsl_atm_close: deallocating vcc 0x%p with vpi %d vci %d", vcc_data, vcc_data->vpi, vcc_data->vci); - - udsl_cancel_send (instance, vcc); - - down (&instance->serialize); /* vs self, udsl_atm_open */ - - tasklet_disable (&instance->receive_tasklet); - list_del (&vcc_data->list); - tasklet_enable (&instance->receive_tasklet); - - kfree_skb (vcc_data->sarb); - vcc_data->sarb = NULL; - - kfree (vcc_data); - vcc->dev_data = NULL; - - vcc->vpi = ATM_VPI_UNSPEC; - vcc->vci = ATM_VCI_UNSPEC; - clear_bit (ATM_VF_READY, &vcc->flags); - clear_bit (ATM_VF_PARTIAL, &vcc->flags); - clear_bit (ATM_VF_ADDR, &vcc->flags); - - up (&instance->serialize); - - dbg ("udsl_atm_close successful"); -} - -static int udsl_atm_ioctl (struct atm_dev *dev, unsigned int cmd, void __user *arg) -{ - switch (cmd) { - case ATM_QUERYLOOP: - return put_user (ATM_LM_NONE, (int __user *)arg) ? -EFAULT : 0; - default: - return -ENOIOCTLCMD; - } -} - - -/********** -** USB ** -**********/ - -static int udsl_set_alternate (struct udsl_instance_data *instance) -{ - down (&instance->serialize); /* vs self */ - if (!instance->firmware_loaded) { - int ret; - - if ((ret = usb_set_interface (instance->usb_dev, 1, 1)) < 0) { - dbg ("udsl_set_alternate: usb_set_interface returned %d!", ret); - up (&instance->serialize); - return ret; - } - instance->firmware_loaded = 1; - } - up (&instance->serialize); - - tasklet_schedule (&instance->receive_tasklet); - - return 0; -} - -static int udsl_usb_ioctl (struct usb_interface *intf, unsigned int code, void *user_data) -{ - struct udsl_instance_data *instance = usb_get_intfdata (intf); - - dbg ("udsl_usb_ioctl entered"); - - if (!instance) { - dbg ("udsl_usb_ioctl: NULL instance!"); - return -ENODEV; - } - - switch (code) { - case UDSL_IOCTL_LINE_UP: - instance->atm_dev->signal = ATM_PHY_SIG_FOUND; - return udsl_set_alternate (instance); - case UDSL_IOCTL_LINE_DOWN: - instance->atm_dev->signal = ATM_PHY_SIG_LOST; - return 0; - default: - return -ENOTTY; - } -} - -static int udsl_usb_probe (struct usb_interface *intf, const struct usb_device_id *id) -{ - struct usb_device *dev = interface_to_usbdev(intf); - int ifnum = intf->altsetting->desc.bInterfaceNumber; - struct udsl_instance_data *instance; - unsigned char mac_str [13]; - int i, length; - char *buf; - - dbg ("udsl_usb_probe: trying device with vendor=0x%x, product=0x%x, ifnum %d", - dev->descriptor.idVendor, dev->descriptor.idProduct, ifnum); - - if ((dev->descriptor.bDeviceClass != USB_CLASS_VENDOR_SPEC) || - (dev->descriptor.idVendor != SPEEDTOUCH_VENDORID) || - (dev->descriptor.idProduct != SPEEDTOUCH_PRODUCTID) || (ifnum != 1)) - return -ENODEV; - - dbg ("udsl_usb_probe: device accepted"); - - /* instance init */ - if (!(instance = kmalloc (sizeof (struct udsl_instance_data), GFP_KERNEL))) { - dbg ("udsl_usb_probe: no memory for instance data!"); - return -ENOMEM; - } - - memset (instance, 0, sizeof (struct udsl_instance_data)); - - init_MUTEX (&instance->serialize); - - instance->usb_dev = dev; - - INIT_LIST_HEAD (&instance->vcc_list); - - spin_lock_init (&instance->receive_lock); - INIT_LIST_HEAD (&instance->spare_receivers); - INIT_LIST_HEAD (&instance->filled_receive_buffers); - - tasklet_init (&instance->receive_tasklet, udsl_process_receive, (unsigned long) instance); - INIT_LIST_HEAD (&instance->spare_receive_buffers); - - skb_queue_head_init (&instance->sndqueue); - - spin_lock_init (&instance->send_lock); - INIT_LIST_HEAD (&instance->spare_senders); - INIT_LIST_HEAD (&instance->spare_send_buffers); - - tasklet_init (&instance->send_tasklet, udsl_process_send, (unsigned long) instance); - INIT_LIST_HEAD (&instance->filled_send_buffers); - - /* receive init */ - for (i = 0; i < num_rcv_urbs; i++) { - struct udsl_receiver *rcv = &(instance->receivers [i]); - - if (!(rcv->urb = usb_alloc_urb (0, GFP_KERNEL))) { - dbg ("udsl_usb_probe: no memory for receive urb %d!", i); - goto fail; - } - - rcv->instance = instance; - - list_add (&rcv->list, &instance->spare_receivers); - } - - for (i = 0; i < num_rcv_bufs; i++) { - struct udsl_receive_buffer *buf = &(instance->receive_buffers [i]); - - if (!(buf->base = kmalloc (rcv_buf_size * ATM_CELL_SIZE, GFP_KERNEL))) { - dbg ("udsl_usb_probe: no memory for receive buffer %d!", i); - goto fail; - } - - list_add (&buf->list, &instance->spare_receive_buffers); - } - - /* send init */ - for (i = 0; i < num_snd_urbs; i++) { - struct udsl_sender *snd = &(instance->senders [i]); - - if (!(snd->urb = usb_alloc_urb (0, GFP_KERNEL))) { - dbg ("udsl_usb_probe: no memory for send urb %d!", i); - goto fail; - } - - snd->instance = instance; - - list_add (&snd->list, &instance->spare_senders); - } - - for (i = 0; i < num_snd_bufs; i++) { - struct udsl_send_buffer *buf = &(instance->send_buffers [i]); - - if (!(buf->base = kmalloc (snd_buf_size * ATM_CELL_SIZE, GFP_KERNEL))) { - dbg ("udsl_usb_probe: no memory for send buffer %d!", i); - goto fail; - } - - list_add (&buf->list, &instance->spare_send_buffers); - } - - /* ATM init */ - if (!(instance->atm_dev = atm_dev_register (udsl_driver_name, &udsl_atm_devops, -1, NULL))) { - dbg ("udsl_usb_probe: failed to register ATM device!"); - goto fail; - } - - instance->atm_dev->ci_range.vpi_bits = ATM_CI_MAX; - instance->atm_dev->ci_range.vci_bits = ATM_CI_MAX; - instance->atm_dev->signal = ATM_PHY_SIG_UNKNOWN; - - /* temp init ATM device, set to 128kbit */ - instance->atm_dev->link_rate = 128 * 1000 / 424; - - /* set MAC address, it is stored in the serial number */ - memset (instance->atm_dev->esi, 0, sizeof (instance->atm_dev->esi)); - if (usb_string (dev, dev->descriptor.iSerialNumber, mac_str, sizeof (mac_str)) == 12) - for (i = 0; i < 6; i++) - instance->atm_dev->esi [i] = (hex2int (mac_str [i * 2]) * 16) + (hex2int (mac_str [i * 2 + 1])); - - /* device description */ - buf = instance->description; - length = sizeof (instance->description); - - if ((i = usb_string (dev, dev->descriptor.iProduct, buf, length)) < 0) - goto finish; - - buf += i; - length -= i; - - i = scnprintf (buf, length, " ("); - buf += i; - length -= i; - - if (length <= 0 || (i = usb_make_path (dev, buf, length)) < 0) - goto finish; - - buf += i; - length -= i; - - snprintf (buf, length, ")"); - -finish: - /* ready for ATM callbacks */ - wmb (); - instance->atm_dev->dev_data = instance; - - usb_set_intfdata (intf, instance); - - return 0; - -fail: - for (i = 0; i < num_snd_bufs; i++) - kfree (instance->send_buffers [i].base); - - for (i = 0; i < num_snd_urbs; i++) - usb_free_urb (instance->senders [i].urb); - - for (i = 0; i < num_rcv_bufs; i++) - kfree (instance->receive_buffers [i].base); - - for (i = 0; i < num_rcv_urbs; i++) - usb_free_urb (instance->receivers [i].urb); - - kfree (instance); - - return -ENOMEM; -} - -static void udsl_usb_disconnect (struct usb_interface *intf) -{ - struct udsl_instance_data *instance = usb_get_intfdata (intf); - struct list_head *pos; - unsigned int count; - int result, i; - - dbg ("udsl_usb_disconnect entered"); - - usb_set_intfdata (intf, NULL); - - if (!instance) { - dbg ("udsl_usb_disconnect: NULL instance!"); - return; - } - - /* receive finalize */ - tasklet_disable (&instance->receive_tasklet); - - for (i = 0; i < num_rcv_urbs; i++) - if ((result = usb_unlink_urb (instance->receivers [i].urb)) < 0) - dbg ("udsl_usb_disconnect: usb_unlink_urb on receive urb %d returned %d!", i, result); - - /* wait for completion handlers to finish */ - do { - count = 0; - spin_lock_irq (&instance->receive_lock); - list_for_each (pos, &instance->spare_receivers) - DEBUG_ON (++count > num_rcv_urbs); - spin_unlock_irq (&instance->receive_lock); - - dbg ("udsl_usb_disconnect: found %u spare receivers", count); - - if (count == num_rcv_urbs) - break; - - set_current_state (TASK_RUNNING); - schedule (); - } while (1); - - /* no need to take the spinlock */ - INIT_LIST_HEAD (&instance->filled_receive_buffers); - INIT_LIST_HEAD (&instance->spare_receive_buffers); - - tasklet_enable (&instance->receive_tasklet); - - for (i = 0; i < num_rcv_urbs; i++) - usb_free_urb (instance->receivers [i].urb); - - for (i = 0; i < num_rcv_bufs; i++) - kfree (instance->receive_buffers [i].base); - - /* send finalize */ - tasklet_disable (&instance->send_tasklet); - - for (i = 0; i < num_snd_urbs; i++) - if ((result = usb_unlink_urb (instance->senders [i].urb)) < 0) - dbg ("udsl_usb_disconnect: usb_unlink_urb on send urb %d returned %d!", i, result); - - /* wait for completion handlers to finish */ - do { - count = 0; - spin_lock_irq (&instance->send_lock); - list_for_each (pos, &instance->spare_senders) - DEBUG_ON (++count > num_snd_urbs); - spin_unlock_irq (&instance->send_lock); - - dbg ("udsl_usb_disconnect: found %u spare senders", count); - - if (count == num_snd_urbs) - break; - - set_current_state (TASK_RUNNING); - schedule (); - } while (1); - - /* no need to take the spinlock */ - INIT_LIST_HEAD (&instance->spare_senders); - INIT_LIST_HEAD (&instance->spare_send_buffers); - instance->current_buffer = NULL; - - tasklet_enable (&instance->send_tasklet); - - for (i = 0; i < num_snd_urbs; i++) - usb_free_urb (instance->senders [i].urb); - - for (i = 0; i < num_snd_bufs; i++) - kfree (instance->send_buffers [i].base); - - wmb (); - instance->usb_dev = NULL; - - /* ATM finalize */ - shutdown_atm_dev (instance->atm_dev); /* frees instance, kills tasklets */ -} - - -/*********** -** init ** -***********/ - -static int __init udsl_usb_init (void) -{ - dbg ("udsl_usb_init: driver version " DRIVER_VERSION); - - if (sizeof (struct udsl_control) > sizeof (((struct sk_buff *)0)->cb)) { - printk (KERN_ERR __FILE__ ": unusable with this kernel!\n"); - return -EIO; - } - - if ((num_rcv_urbs > UDSL_MAX_RCV_URBS) || (num_snd_urbs > UDSL_MAX_SND_URBS) || - (num_rcv_bufs > UDSL_MAX_RCV_BUFS) || (num_snd_bufs > UDSL_MAX_SND_BUFS) || - (rcv_buf_size > UDSL_MAX_RCV_BUF_SIZE) || (snd_buf_size > UDSL_MAX_SND_BUF_SIZE)) - return -EINVAL; - - return usb_register (&udsl_usb_driver); -} - -static void __exit udsl_usb_cleanup (void) -{ - dbg ("udsl_usb_cleanup entered"); - - usb_deregister (&udsl_usb_driver); -} - -module_init (udsl_usb_init); -module_exit (udsl_usb_cleanup); - -MODULE_AUTHOR (DRIVER_AUTHOR); -MODULE_DESCRIPTION (DRIVER_DESC); -MODULE_LICENSE ("GPL"); -MODULE_VERSION (DRIVER_VERSION); - - -/************ -** debug ** -************/ - -#ifdef VERBOSE_DEBUG -static int udsl_print_packet (const unsigned char *data, int len) -{ - unsigned char buffer [256]; - int i = 0, j = 0; - - for (i = 0; i < len;) { - buffer [0] = '\0'; - sprintf (buffer, "%.3d :", i); - for (j = 0; (j < 16) && (i < len); j++, i++) { - sprintf (buffer, "%s %2.2x", buffer, data [i]); - } - dbg ("%s", buffer); - } - return i; -} -#endif -- cgit v1.2.3 From 6897bdb5816c602a4f081c1b281b643f69229c83 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Tue, 5 Oct 2004 19:09:35 +0100 Subject: USB: Reformat usb-atm code and rework SpeedTouch firmware loading. Now it's all from a single kernel thread started directly by the probe routine, and it checks it can find _all_ the firmware it wants before starting to poke at the modem. Signed-off-by: David Woodhouse Signed-off-by: Greg Kroah-Hartman --- drivers/usb/atm/speedtch.c | 602 +++++++++++++++--------------- drivers/usb/atm/usb_atm.c | 908 +++++++++++++++++++++++---------------------- drivers/usb/atm/usb_atm.h | 27 +- 3 files changed, 760 insertions(+), 777 deletions(-) diff --git a/drivers/usb/atm/speedtch.c b/drivers/usb/atm/speedtch.c index 29be861235e0..696541b57c88 100644 --- a/drivers/usb/atm/speedtch.c +++ b/drivers/usb/atm/speedtch.c @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -31,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -57,14 +59,8 @@ # define USE_FW_LOADER #endif -#ifdef DEBUG -#define DEBUG_ON(x) BUG_ON(x) -#else -#define DEBUG_ON(x) do { if (x); } while (0) -#endif - #ifdef VERBOSE_DEBUG -static int udsl_print_packet (const unsigned char *data, int len); +static int udsl_print_packet(const unsigned char *data, int len); #define PACKETDEBUG(arg...) udsl_print_packet (arg) #define vdbg(arg...) dbg (arg) #else @@ -76,7 +72,7 @@ static int udsl_print_packet (const unsigned char *data, int len); #define DRIVER_VERSION "1.8" #define DRIVER_DESC "Alcatel SpeedTouch USB driver version " DRIVER_VERSION -static const char speedtch_driver_name [] = "speedtch"; +static const char speedtch_driver_name[] = "speedtch"; #define SPEEDTOUCH_VENDORID 0x06b9 #define SPEEDTOUCH_PRODUCTID 0x4061 @@ -85,11 +81,11 @@ static const char speedtch_driver_name [] = "speedtch"; #define CTRL_TIMEOUT (2*HZ) #define DATA_TIMEOUT (2*HZ) -#define OFFSET_7 0 /* size 1 */ -#define OFFSET_b 1 /* size 8 */ -#define OFFSET_d 9 /* size 4 */ -#define OFFSET_e 13 /* size 1 */ -#define OFFSET_f 14 /* size 1 */ +#define OFFSET_7 0 /* size 1 */ +#define OFFSET_b 1 /* size 8 */ +#define OFFSET_d 9 /* size 4 */ +#define OFFSET_e 13 /* size 1 */ +#define OFFSET_f 14 /* size 1 */ #define TOTAL 15 #define SIZE_7 1 @@ -101,11 +97,11 @@ static const char speedtch_driver_name [] = "speedtch"; static int dl_512_first = 0; static int sw_buffering = 0; -module_param (dl_512_first, bool, 0444); -MODULE_PARM_DESC (dl_512_first, "Read 512 bytes before sending firmware"); +module_param(dl_512_first, bool, 0444); +MODULE_PARM_DESC(dl_512_first, "Read 512 bytes before sending firmware"); -module_param (sw_buffering, uint, 0444); -MODULE_PARM_DESC (sw_buffering, "Enable software buffering"); +module_param(sw_buffering, uint, 0444); +MODULE_PARM_DESC(sw_buffering, "Enable software buffering"); #define UDSL_IOCTL_LINE_UP 1 #define UDSL_IOCTL_LINE_DOWN 2 @@ -116,112 +112,113 @@ MODULE_PARM_DESC (sw_buffering, "Enable software buffering"); #define hex2int(c) ( (c >= '0') && (c <= '9') ? (c - '0') : ((c & 0xf) + 9) ) -static struct usb_device_id speedtch_usb_ids [] = { - { USB_DEVICE (SPEEDTOUCH_VENDORID, SPEEDTOUCH_PRODUCTID) }, - { } +static struct usb_device_id speedtch_usb_ids[] = { + {USB_DEVICE(SPEEDTOUCH_VENDORID, SPEEDTOUCH_PRODUCTID)}, + {} }; -MODULE_DEVICE_TABLE (usb, speedtch_usb_ids); - +MODULE_DEVICE_TABLE(usb, speedtch_usb_ids); struct speedtch_instance_data { struct udsl_instance_data u; - u16 revision; - /* Status */ struct urb *int_urb; unsigned char int_data[16]; struct work_struct poll_work; struct timer_list poll_timer; - char fwname[25]; }; /* USB */ -static int speedtch_usb_probe (struct usb_interface *intf, const struct usb_device_id *id); -static void speedtch_usb_disconnect (struct usb_interface *intf); -static int speedtch_usb_ioctl (struct usb_interface *intf, unsigned int code, void *user_data); -static void speedtch_handle_int (struct urb *urb, struct pt_regs *regs); -static void speedtch_poll_status (struct speedtch_instance_data *instance); +static int speedtch_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id); +static void speedtch_usb_disconnect(struct usb_interface *intf); +static int speedtch_usb_ioctl(struct usb_interface *intf, unsigned int code, + void *user_data); +static void speedtch_handle_int(struct urb *urb, struct pt_regs *regs); +static void speedtch_poll_status(struct speedtch_instance_data *instance); static struct usb_driver speedtch_usb_driver = { - .owner = THIS_MODULE, - .name = speedtch_driver_name, - .probe = speedtch_usb_probe, - .disconnect = speedtch_usb_disconnect, - .ioctl = speedtch_usb_ioctl, - .id_table = speedtch_usb_ids, + .owner = THIS_MODULE, + .name = speedtch_driver_name, + .probe = speedtch_usb_probe, + .disconnect = speedtch_usb_disconnect, + .ioctl = speedtch_usb_ioctl, + .id_table = speedtch_usb_ids, }; - /*************** ** firmware ** ***************/ -static void speedtch_got_firmware (struct speedtch_instance_data *instance, int got_it) +static void speedtch_got_firmware(struct speedtch_instance_data *instance, + int got_it) { int err; struct usb_interface *intf; - down (&instance->u.serialize); /* vs self, speedtch_firmware_start */ + down(&instance->u.serialize); /* vs self, speedtch_firmware_start */ if (instance->u.status == UDSL_LOADED_FIRMWARE) goto out; if (!got_it) { instance->u.status = UDSL_NO_FIRMWARE; goto out; } - if ((err = usb_set_interface (instance->u.usb_dev, 1, 1)) < 0) { - dbg ("speedtch_got_firmware: usb_set_interface returned %d!", err); + if ((err = usb_set_interface(instance->u.usb_dev, 1, 1)) < 0) { + dbg("speedtch_got_firmware: usb_set_interface returned %d!", + err); instance->u.status = UDSL_NO_FIRMWARE; goto out; } /* Set up interrupt endpoint */ intf = usb_ifnum_to_if(instance->u.usb_dev, 0); - if (intf && !usb_driver_claim_interface (&speedtch_usb_driver, intf, NULL)) { + if (intf && !usb_driver_claim_interface(&speedtch_usb_driver, intf, NULL)) { instance->int_urb = usb_alloc_urb(0, GFP_KERNEL); if (instance->int_urb) { usb_fill_int_urb(instance->int_urb, instance->u.usb_dev, usb_rcvintpipe(instance->u.usb_dev, SPEEDTCH_ENDPOINT_INT), - instance->int_data, sizeof(instance->int_data), + instance->int_data, + sizeof(instance->int_data), speedtch_handle_int, instance, 50); err = usb_submit_urb(instance->int_urb, GFP_KERNEL); if (err) { /* Doesn't matter; we'll poll anyway */ - dbg ("speedtch_got_firmware: Submission of interrupt URB failed %d", err); + dbg("speedtch_got_firmware: Submission of interrupt URB failed %d", err); usb_free_urb(instance->int_urb); instance->int_urb = NULL; - usb_driver_release_interface (&speedtch_usb_driver, intf); + usb_driver_release_interface(&speedtch_usb_driver, intf); } } } /* Start status polling */ - mod_timer(&instance->poll_timer, jiffies + (1*HZ)); + mod_timer(&instance->poll_timer, jiffies + (1 * HZ)); instance->u.status = UDSL_LOADED_FIRMWARE; - tasklet_schedule (&instance->u.receive_tasklet); -out: - up (&instance->u.serialize); - wake_up_interruptible (&instance->u.firmware_waiters); + tasklet_schedule(&instance->u.receive_tasklet); + out: + up(&instance->u.serialize); + wake_up_interruptible(&instance->u.firmware_waiters); } -static int speedtch_set_swbuff (struct speedtch_instance_data *instance, int state) +static int speedtch_set_swbuff(struct speedtch_instance_data *instance, + int state) { struct usb_device *dev = instance->u.usb_dev; int ret; ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), - 0x32, 0x40, state?0x01:0x00, + 0x32, 0x40, state ? 0x01 : 0x00, 0x00, NULL, 0, 100); if (ret < 0) { printk("Warning: %sabling SW buffering: usb_control_msg returned %d\n", - state?"En":"Dis", ret); + state ? "En" : "Dis", ret); return ret; } - dbg("speedtch_set_swbuff: %sbled SW buffering", state?"En":"Dis"); + dbg("speedtch_set_swbuff: %sbled SW buffering", state ? "En" : "Dis"); return 0; } @@ -232,35 +229,41 @@ static void speedtch_test_sequence(struct speedtch_instance_data *instance) int ret; /* URB 147 */ - buf[0] = 0x1c; buf[1] = 0x50; + buf[0] = 0x1c; + buf[1] = 0x50; ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), 0x01, 0x40, 0x0b, 0x00, buf, 2, 100); if (ret < 0) printk(KERN_WARNING "%s failed on URB147: %d\n", __func__, ret); /* URB 148 */ - buf[0] = 0x32; buf[1] = 0x00; + buf[0] = 0x32; + buf[1] = 0x00; ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), 0x01, 0x40, 0x02, 0x00, buf, 2, 100); if (ret < 0) printk(KERN_WARNING "%s failed on URB148: %d\n", __func__, ret); /* URB 149 */ - buf[0] = 0x01; buf[1] = 0x00; buf[2] = 0x01; + buf[0] = 0x01; + buf[1] = 0x00; + buf[2] = 0x01; ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), 0x01, 0x40, 0x03, 0x00, buf, 3, 100); if (ret < 0) printk(KERN_WARNING "%s failed on URB149: %d\n", __func__, ret); /* URB 150 */ - buf[0] = 0x01; buf[1] = 0x00; buf[2] = 0x01; + buf[0] = 0x01; + buf[1] = 0x00; + buf[2] = 0x01; ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), 0x01, 0x40, 0x04, 0x00, buf, 3, 100); if (ret < 0) printk(KERN_WARNING "%s failed on URB150: %d\n", __func__, ret); } -static int speedtch_start_synchro (struct speedtch_instance_data *instance) +static int speedtch_start_synchro(struct speedtch_instance_data *instance) { struct usb_device *dev = instance->u.usb_dev; unsigned char buf[2]; @@ -278,37 +281,36 @@ static int speedtch_start_synchro (struct speedtch_instance_data *instance) return 0; } -static void speedtch_handle_int (struct urb *urb, struct pt_regs *regs) +static void speedtch_handle_int(struct urb *urb, struct pt_regs *regs) { struct speedtch_instance_data *instance = urb->context; unsigned int count = urb->actual_length; int ret; /* The magic interrupt for "up state" */ - const static unsigned char up_int[6] = { 0xa1, 0x00, 0x01, 0x00, 0x00, 0x00}; + const static unsigned char up_int[6] = { 0xa1, 0x00, 0x01, 0x00, 0x00, 0x00 }; /* The magic interrupt for "down state" */ - const static unsigned char down_int[6] = { 0xa1, 0x00, 0x00, 0x00, 0x00, 0x00}; - + const static unsigned char down_int[6] = { 0xa1, 0x00, 0x00, 0x00, 0x00, 0x00 }; switch (urb->status) { - case 0: - /* success */ - break; - case -ECONNRESET: - case -ENOENT: - case -ESHUTDOWN: - /* this urb is terminated, clean up */ - dbg("%s - urb shutting down with status: %d", __func__, urb->status); - return; - default: - dbg("%s - nonzero urb status received: %d", __func__, urb->status); - goto exit; - } - - if (count < 6) { - dbg("%s - int packet too short", __func__); - goto exit; - } + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated; clean up */ + dbg("%s - urb shutting down with status: %d", __func__, urb->status); + return; + default: + dbg("%s - nonzero urb status received: %d", __func__, urb->status); + goto exit; + } + + if (count < 6) { + dbg("%s - int packet too short", __func__); + goto exit; + } if (!memcmp(up_int, instance->int_data, 6)) { del_timer(&instance->poll_timer); @@ -320,7 +322,7 @@ static void speedtch_handle_int (struct urb *urb, struct pt_regs *regs) int i; printk(KERN_DEBUG "Unknown interrupt packet of %d bytes:", count); - for (i=0; i < count; i++) + for (i = 0; i < count; i++) printk(" %02x", instance->int_data[i]); printk("\n"); } @@ -331,52 +333,57 @@ static void speedtch_handle_int (struct urb *urb, struct pt_regs *regs) if (!instance->int_urb) return; - ret = usb_submit_urb (urb, GFP_ATOMIC); + ret = usb_submit_urb(urb, GFP_ATOMIC); if (ret) - err ("%s - usb_submit_urb failed with result %d", - __func__, ret); + err("%s - usb_submit_urb failed with result %d", __func__, ret); } -static int speedtch_get_status(struct speedtch_instance_data *instance, unsigned char *buf) +static int speedtch_get_status(struct speedtch_instance_data *instance, + unsigned char *buf) { struct usb_device *dev = instance->u.usb_dev; int ret; - memset(buf,0,TOTAL); + memset(buf, 0, TOTAL); ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), - 0x12, 0xc0, 0x07, 0x00, buf+OFFSET_7, SIZE_7, CTRL_TIMEOUT); - if (ret<0) { + 0x12, 0xc0, 0x07, 0x00, buf + OFFSET_7, SIZE_7, + CTRL_TIMEOUT); + if (ret < 0) { dbg("MSG 7 failed"); - return(ret); + return ret; } ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), - 0x12, 0xc0, 0x0b, 0x00, buf+OFFSET_b, SIZE_b, CTRL_TIMEOUT); - if (ret<0) { + 0x12, 0xc0, 0x0b, 0x00, buf + OFFSET_b, SIZE_b, + CTRL_TIMEOUT); + if (ret < 0) { dbg("MSG B failed"); - return(ret); + return ret; } ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), - 0x12, 0xc0, 0x0d, 0x00, buf+OFFSET_d, SIZE_d, CTRL_TIMEOUT); - if (ret<0) { + 0x12, 0xc0, 0x0d, 0x00, buf + OFFSET_d, SIZE_d, + CTRL_TIMEOUT); + if (ret < 0) { dbg("MSG D failed"); - return(ret); + return ret; } ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), - 0x01, 0xc0, 0x0e, 0x00, buf+OFFSET_e, SIZE_e, CTRL_TIMEOUT); - if (ret<0) { + 0x01, 0xc0, 0x0e, 0x00, buf + OFFSET_e, SIZE_e, + CTRL_TIMEOUT); + if (ret < 0) { dbg("MSG E failed"); - return(ret); + return ret; } ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), - 0x01, 0xc0, 0x0f, 0x00, buf+OFFSET_f, SIZE_f, CTRL_TIMEOUT); - if (ret<0) { + 0x01, 0xc0, 0x0f, 0x00, buf + OFFSET_f, SIZE_f, + CTRL_TIMEOUT); + if (ret < 0) { dbg("MSG F failed"); - return(ret); + return ret; } return 0; @@ -389,7 +396,8 @@ static void speedtch_poll_status(struct speedtch_instance_data *instance) ret = speedtch_get_status(instance, buf); if (ret) { - printk(KERN_WARNING "SpeedTouch: Error %d fetching device status\n", ret); + printk(KERN_WARNING + "SpeedTouch: Error %d fetching device status\n", ret); return; } @@ -419,20 +427,21 @@ static void speedtch_poll_status(struct speedtch_instance_data *instance) case 0x20: if (instance->u.atm_dev->signal != ATM_PHY_SIG_FOUND) { - int down_speed = buf[OFFSET_b] | (buf[OFFSET_b+1]<<8) - | (buf[OFFSET_b+2]<<16) | (buf[OFFSET_b+3]<<24); - int up_speed = buf[OFFSET_b+4] | (buf[OFFSET_b+5]<<8) - | (buf[OFFSET_b+6]<<16) | (buf[OFFSET_b+7]<<24); - - if(!(down_speed & 0x0000ffff) && - !(up_speed & 0x0000ffff)) { - down_speed>>=16; - up_speed>>=16; + int down_speed = buf[OFFSET_b] | (buf[OFFSET_b + 1] << 8) + | (buf[OFFSET_b + 2] << 16) | (buf[OFFSET_b + 3] << 24); + int up_speed = buf[OFFSET_b + 4] | (buf[OFFSET_b + 5] << 8) + | (buf[OFFSET_b + 6] << 16) | (buf[OFFSET_b + 7] << 24); + + if (!(down_speed & 0x0000ffff) && + !(up_speed & 0x0000ffff)) { + down_speed >>= 16; + up_speed >>= 16; } instance->u.atm_dev->link_rate = down_speed * 1000 / 424; instance->u.atm_dev->signal = ATM_PHY_SIG_FOUND; - printk(KERN_NOTICE "ADSL line is up (%d Kib/s down | %d Kib/s up)\n", + printk(KERN_NOTICE + "ADSL line is up (%d Kib/s down | %d Kib/s up)\n", down_speed, up_speed); } break; @@ -446,176 +455,111 @@ static void speedtch_poll_status(struct speedtch_instance_data *instance) } } -static void speedtch_timer_poll (unsigned long data) +static void speedtch_timer_poll(unsigned long data) { - struct speedtch_instance_data *instance = (struct speedtch_instance_data *) data; + struct speedtch_instance_data *instance = (void *)data; schedule_work(&instance->poll_work); - mod_timer(&instance->poll_timer, jiffies + (5*HZ)); + mod_timer(&instance->poll_timer, jiffies + (5 * HZ)); } -static void speedtch_firmware_load(const struct firmware *fw1, void *context) +#ifdef USE_FW_LOADER + +static void speedtch_upload_firmware(struct speedtch_instance_data *instance, + const struct firmware *fw1, + const struct firmware *fw2) { - const struct firmware *fw2; unsigned char *buffer; - struct speedtch_instance_data *instance = context; + struct usb_device *usb_dev = instance->u.usb_dev; struct usb_interface *intf; - struct usb_device *dev = instance->u.usb_dev; int actual_length, ret; - int pg; + int offset; - dbg ("speedtch_firmware_load"); + dbg("speedtch_upload_firmware"); - ret = -1; - - BUG_ON(!instance); - - if (!(intf = usb_ifnum_to_if (dev, 2))) { - dbg ("speedtch_firmware_load: interface not found!"); + if (!(intf = usb_ifnum_to_if(usb_dev, 2))) { + dbg("speedtch_upload_firmware: interface not found!"); goto fail; } - if (!(buffer = kmalloc (0x1000, GFP_KERNEL))) { - dbg ("speedtch_firmware_load: no memory for buffer!"); + if (!(buffer = (unsigned char *)__get_free_page(GFP_KERNEL))) { + dbg("speedtch_upload_firmware: no memory for buffer!"); goto fail; } - if (!fw1) { - dbg ("speedtch_firmware_load: no firmware 'speedtch_boot_rev%d.%02x", - instance->revision >> 8, instance->revision & 0xff); - - snprintf(instance->fwname, sizeof(instance->fwname), "speedtch_boot_rev%d", - instance->revision >> 8); - - ret = request_firmware(&fw1, instance->fwname, &dev->dev); - if (ret < 0) { - dbg ("speedtch_firmware_load: no firmware '%s'", instance->fwname); - - strlcpy(instance->fwname, "speedtch_boot", sizeof(instance->fwname)); - ret = request_firmware(&fw1, instance->fwname, &dev->dev); - } - if (ret < 0) { - dbg ("speedtch_firmware_load: no firmware '%s'", instance->fwname); - printk(KERN_INFO "speedtch: No boot firmware found. Assuming userspace firmware loader\n"); - goto fail_buf; - } - } - snprintf(instance->fwname, sizeof(instance->fwname), "speedtch_main_rev%d.%02x", - instance->revision >> 8, instance->revision & 0xff); - ret = request_firmware(&fw2, instance->fwname, &dev->dev); - if (ret < 0) { - dbg ("speedtch_firmware_load: no firmware '%s'", instance->fwname); - snprintf(instance->fwname, sizeof(instance->fwname), "speedtch_main_rev%d", - instance->revision >> 8); - - ret = request_firmware(&fw2, instance->fwname, &dev->dev); - } - if (ret < 0) { - dbg ("speedtch_firmware_load: no firmware '%s'", instance->fwname); - - strlcpy(instance->fwname, "speedtch_main", sizeof(instance->fwname)); - ret = request_firmware(&fw2, instance->fwname, &dev->dev); - } - if (ret < 0) { - dbg ("speedtch_firmware_load: no firmware '%s'", instance->fwname); - printk(KERN_INFO "speedtch: No main firmware found. Assuming userspace firmware loader\n"); - goto fail_buf; - } - - /* OK, we have the firmware. So try to claim interface #2 and actually - try uploading it. There's a slight possibility that the userspace - modem_run could be running too, and may have beaten us to it */ - if ((ret = usb_driver_claim_interface (&speedtch_usb_driver, intf, NULL)) < 0) { - dbg ("speedtch_firmware_start: interface in use (%d)!", ret); - goto fail_fw2; + /* A user-space firmware loader may already have claimed interface #2 */ + if ((ret = + usb_driver_claim_interface(&speedtch_usb_driver, intf, NULL)) < 0) { + dbg("speedtch_upload_firmware: interface in use (%d)!", ret); + goto fail_free; } /* URB 7 */ - if (dl_512_first) { /* some modems need a read before writing the firmware */ - ret = usb_bulk_msg (instance->u.usb_dev, - usb_rcvbulkpipe (instance->u.usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE), - buffer, - 0x200, - &actual_length, - 2 * HZ); + if (dl_512_first) { /* some modems need a read before writing the firmware */ + ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE), + buffer, 0x200, &actual_length, 2 * HZ); if (ret < 0 && ret != -ETIMEDOUT) - dbg ("speedtch_firmware_load: read BLOCK0 from modem failed (%d)!", ret); + dbg("speedtch_upload_firmware: read BLOCK0 from modem failed (%d)!", ret); else - dbg("speedtch_firmware_load: BLOCK0 downloaded (%d bytes)", ret); + dbg("speedtch_upload_firmware: BLOCK0 downloaded (%d bytes)", ret); } /* URB 8 : both leds are static green */ - for (pg = 0; pg * 0x1000 < fw1->size; pg++) { - int thislen = min_t(int, 0x1000, fw1->size - (pg*0x1000)); - memcpy(buffer, fw1->data + (pg*0x1000), thislen); + for (offset = 0; offset < fw1->size; offset += PAGE_SIZE) { + int thislen = min_t(int, PAGE_SIZE, fw1->size - offset); + memcpy(buffer, fw1->data + offset, thislen); - ret = usb_bulk_msg (instance->u.usb_dev, - usb_sndbulkpipe (instance->u.usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE), - buffer, - thislen, - &actual_length, - DATA_TIMEOUT); + ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE), + buffer, thislen, &actual_length, DATA_TIMEOUT); if (ret < 0) { - dbg ("speedtch_firmware_load: write BLOCK1 to modem failed (%d)!", ret); + dbg("speedtch_upload_firmware: write BLOCK1 to modem failed (%d)!", ret); goto fail_release; } - dbg("speedtch_firmware_load: BLOCK1 uploaded (%d bytes)", fw1->size); + dbg("speedtch_upload_firmware: BLOCK1 uploaded (%d bytes)", fw1->size); } /* USB led blinking green, ADSL led off */ /* URB 11 */ - ret = usb_bulk_msg (instance->u.usb_dev, - usb_rcvbulkpipe (instance->u.usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE), - buffer, - 0x200, - &actual_length, - DATA_TIMEOUT); + ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE), + buffer, 0x200, &actual_length, DATA_TIMEOUT); if (ret < 0) { - dbg ("speedtch_firmware_load: read BLOCK2 from modem failed (%d)!", ret); + dbg("speedtch_upload_firmware: read BLOCK2 from modem failed (%d)!", ret); goto fail_release; } - dbg("speedtch_firmware_load: BLOCK2 downloaded (%d bytes)", actual_length); + dbg("speedtch_upload_firmware: BLOCK2 downloaded (%d bytes)", actual_length); /* URBs 12 to 139 - USB led blinking green, ADSL led off */ - for (pg = 0; pg * 0x1000 < fw2->size; pg++) { - int thislen = min_t(int, 0x1000, fw2->size - (pg*0x1000)); - memcpy(buffer, fw2->data + (pg*0x1000), thislen); + for (offset = 0; offset < fw2->size; offset += PAGE_SIZE) { + int thislen = min_t(int, PAGE_SIZE, fw2->size - offset); + memcpy(buffer, fw2->data + offset, thislen); - ret = usb_bulk_msg (instance->u.usb_dev, - usb_sndbulkpipe (instance->u.usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE), - buffer, - thislen, - &actual_length, - DATA_TIMEOUT); + ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE), + buffer, thislen, &actual_length, DATA_TIMEOUT); if (ret < 0) { - dbg ("speedtch_firmware_load: write BLOCK3 to modem failed (%d)!", ret); + dbg("speedtch_upload_firmware: write BLOCK3 to modem failed (%d)!", ret); goto fail_release; } } - dbg("speedtch_firmware_load: BLOCK3 uploaded (%d bytes)", fw2->size); + dbg("speedtch_upload_firmware: BLOCK3 uploaded (%d bytes)", fw2->size); /* USB led static green, ADSL led static red */ /* URB 142 */ - ret = usb_bulk_msg (instance->u.usb_dev, - usb_rcvbulkpipe (instance->u.usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE), - buffer, - 0x200, - &actual_length, - DATA_TIMEOUT); + ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE), + buffer, 0x200, &actual_length, DATA_TIMEOUT); if (ret < 0) { - dbg("speedtch_firmware_load: read BLOCK4 from modem failed (%d)!", ret); + dbg("speedtch_upload_firmware: read BLOCK4 from modem failed (%d)!", ret); goto fail_release; } /* success */ - dbg("speedtch_firmware_load: BLOCK4 downloaded (%d bytes)", actual_length); + dbg("speedtch_upload_firmware: BLOCK4 downloaded (%d bytes)", actual_length); /* Delay to allow firmware to start up. We can do this here because we're in our own kernel thread anyway. */ @@ -634,69 +578,112 @@ static void speedtch_firmware_load(const struct firmware *fw1, void *context) speedtch_got_firmware(instance, 1); - goto fail_fw2; /* The got_firmware(0) is a NOP anyway */ + free_page((unsigned long)buffer); + return; fail_release: - /* We only release interface #2 if loading the firmware failed; we don't - do it if we succeeded. This prevents the userspace modem_run tool from - trying to load the firmware itself */ - usb_driver_release_interface (&speedtch_usb_driver, intf); - fail_fw2: - release_firmware(fw2); - fail_buf: - kfree (buffer); + /* Only release interface #2 if uploading failed; we don't release it + we succeeded. This prevents the userspace tools from trying to load + the firmware themselves */ + usb_driver_release_interface(&speedtch_usb_driver, intf); + fail_free: + kfree(buffer); fail: - speedtch_got_firmware (instance, 0); + speedtch_got_firmware(instance, 0); +} + +static int speedtch_find_firmware(struct speedtch_instance_data + *instance, int phase, + const struct firmware **fw_p) +{ + char buf[24]; + const u16 bcdDevice = instance->u.usb_dev->descriptor.bcdDevice; + + sprintf(buf, "speedtch-%d.bin.%x.%02x", phase, bcdDevice >> 8, + bcdDevice & 0xff); + dbg("speedtch_find_firmware: looking for %s\n", buf); + + if (!request_firmware(fw_p, buf, &instance->u.usb_dev->dev)) + return 0; + + sprintf(buf, "speedtch-%d.bin.%x", phase, bcdDevice >> 8); + dbg("speedtch_find_firmware: looking for %s\n", buf); + + if (!request_firmware(fw_p, buf, &instance->u.usb_dev->dev)) + return 0; + + sprintf(buf, "speedtch-%d.bin", phase); + dbg("speedtch_find_firmware: looking for %s\n", buf); + + if (!request_firmware(fw_p, buf, &instance->u.usb_dev->dev)) + return 0; + + dev_warn(&instance->u.usb_dev->dev, "no stage %d firmware found!", phase); + return -ENOENT; +} + +static int speedtch_load_firmware(void *arg) +{ + const struct firmware *fw1, *fw2; + struct speedtch_instance_data *instance = arg; + + BUG_ON(!instance); + + daemonize("firmware/speedtch"); + + if (!speedtch_find_firmware(instance, 1, &fw1)) { + if (!speedtch_find_firmware(instance, 2, &fw2)) { + speedtch_upload_firmware(instance, fw1, fw2); + release_firmware(fw2); + } + release_firmware(fw1); + } + udsl_put_instance(&instance->u); + return 0; } +#endif /* USE_FW_LOADER */ -static void speedtch_firmware_start (struct speedtch_instance_data *instance) +static void speedtch_firmware_start(struct speedtch_instance_data *instance) { #ifdef USE_FW_LOADER int ret; #endif - dbg ("speedtch_firmware_start"); + dbg("speedtch_firmware_start"); - down (&instance->u.serialize); /* vs self, speedtch_got_firmware */ + down(&instance->u.serialize); /* vs self, speedtch_got_firmware */ if (instance->u.status >= UDSL_LOADING_FIRMWARE) { - up (&instance->u.serialize); + up(&instance->u.serialize); return; } instance->u.status = UDSL_LOADING_FIRMWARE; - up (&instance->u.serialize); + up(&instance->u.serialize); udsl_get_instance(&instance->u); #ifdef USE_FW_LOADER - snprintf(instance->fwname, sizeof(instance->fwname), "speedtch_boot_rev%d.%02x", - instance->revision >> 8, instance->revision & 0xff); - printk("Look for %s\n", instance->fwname); - ret = request_firmware_nowait (THIS_MODULE, - instance->fwname, - &instance->u.usb_dev->dev, - instance, - speedtch_firmware_load); + ret = kernel_thread(speedtch_load_firmware, instance, + CLONE_FS | CLONE_FILES); if (ret >= 0) - return; /* OK */ - - dbg ("speedtch_firmware_start: request_firmware_nowait failed (%d)!", ret); + return; /* OK */ + dbg("speedtch_firmware_start: kernel_thread failed (%d)!", ret); /* Just pretend it never happened... hope modem_run happens */ -#endif /* USE_FW_LOADER */ - speedtch_got_firmware (instance, 0); +#endif /* USE_FW_LOADER */ + speedtch_got_firmware(instance, 0); udsl_put_instance(&instance->u); } -static int speedtch_firmware_wait (struct udsl_instance_data *instance) +static int speedtch_firmware_wait(struct udsl_instance_data *instance) { - speedtch_firmware_start ((void *) instance); + speedtch_firmware_start((void *)instance); - if (wait_event_interruptible (instance->firmware_waiters, instance->status != UDSL_LOADING_FIRMWARE) < 0) + if (wait_event_interruptible(instance->firmware_waiters, instance->status != UDSL_LOADING_FIRMWARE) < 0) return -ERESTARTSYS; return (instance->status == UDSL_LOADED_FIRMWARE) ? 0 : -EAGAIN; @@ -706,21 +693,22 @@ static int speedtch_firmware_wait (struct udsl_instance_data *instance) ** USB ** **********/ -static int speedtch_usb_ioctl (struct usb_interface *intf, unsigned int code, void *user_data) +static int speedtch_usb_ioctl(struct usb_interface *intf, unsigned int code, + void *user_data) { - struct speedtch_instance_data *instance = usb_get_intfdata (intf); + struct speedtch_instance_data *instance = usb_get_intfdata(intf); - dbg ("speedtch_usb_ioctl entered"); + dbg("speedtch_usb_ioctl entered"); if (!instance) { - dbg ("speedtch_usb_ioctl: NULL instance!"); + dbg("speedtch_usb_ioctl: NULL instance!"); return -ENODEV; } switch (code) { case UDSL_IOCTL_LINE_UP: instance->u.atm_dev->signal = ATM_PHY_SIG_FOUND; - speedtch_got_firmware (instance, 1); + speedtch_got_firmware(instance, 1); return (instance->u.status == UDSL_LOADED_FIRMWARE) ? 0 : -EIO; case UDSL_IOCTL_LINE_DOWN: instance->u.atm_dev->signal = ATM_PHY_SIG_LOST; @@ -730,37 +718,38 @@ static int speedtch_usb_ioctl (struct usb_interface *intf, unsigned int code, vo } } -static int speedtch_usb_probe (struct usb_interface *intf, const struct usb_device_id *id) +static int speedtch_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) { struct usb_device *dev = interface_to_usbdev(intf); int ifnum = intf->altsetting->desc.bInterfaceNumber; struct speedtch_instance_data *instance; - unsigned char mac_str [13]; + unsigned char mac_str[13]; int ret, i; char buf7[SIZE_7]; - dbg ("speedtch_usb_probe: trying device with vendor=0x%x, product=0x%x, ifnum %d", - dev->descriptor.idVendor, dev->descriptor.idProduct, ifnum); + dbg("speedtch_usb_probe: trying device with vendor=0x%x, product=0x%x, ifnum %d", dev->descriptor.idVendor, dev->descriptor.idProduct, ifnum); if ((dev->descriptor.bDeviceClass != USB_CLASS_VENDOR_SPEC) || (dev->descriptor.idVendor != SPEEDTOUCH_VENDORID) || (dev->descriptor.idProduct != SPEEDTOUCH_PRODUCTID) || (ifnum != 1)) return -ENODEV; - dbg ("speedtch_usb_probe: device accepted"); + dbg("speedtch_usb_probe: device accepted"); /* instance init */ - if (!(instance = kmalloc (sizeof (struct speedtch_instance_data), GFP_KERNEL))) { - dbg ("speedtch_usb_probe: no memory for instance data!"); + instance = kmalloc(sizeof(*instance), GFP_KERNEL); + if (!instance) { + dbg("speedtch_usb_probe: no memory for instance data!"); return -ENOMEM; } - memset (instance, 0, sizeof (struct speedtch_instance_data)); + memset(instance, 0, sizeof(struct speedtch_instance_data)); - if ((ret = usb_set_interface (dev, 0, 0)) < 0) + if ((ret = usb_set_interface(dev, 0, 0)) < 0) goto fail; - if ((ret = usb_set_interface (dev, 2, 0)) < 0) + if ((ret = usb_set_interface(dev, 2, 0)) < 0) goto fail; instance->u.data_endpoint = SPEEDTCH_ENDPOINT_DATA; @@ -777,60 +766,50 @@ static int speedtch_usb_probe (struct usb_interface *intf, const struct usb_devi INIT_WORK(&instance->poll_work, (void *)speedtch_poll_status, instance); - switch (dev->descriptor.bcdDevice) { - case 0x0000: - case 0x0200: - case 0x0400: - case 0x0401: - instance->revision = dev->descriptor.bcdDevice; - break; - default: - instance->revision = 0x200; - printk(KERN_INFO "Unexpected SpeedTouch revision %04x, treating as Rev 2.00.\n", - dev->descriptor.bcdDevice); - break; - } - /* set MAC address, it is stored in the serial number */ - memset (instance->u.atm_dev->esi, 0, sizeof (instance->u.atm_dev->esi)); - if (usb_string (dev, dev->descriptor.iSerialNumber, mac_str, sizeof (mac_str)) == 12) + memset(instance->u.atm_dev->esi, 0, sizeof(instance->u.atm_dev->esi)); + if (usb_string(dev, dev->descriptor.iSerialNumber, mac_str, sizeof(mac_str)) == 12) { for (i = 0; i < 6; i++) - instance->u.atm_dev->esi [i] = (hex2int (mac_str [i * 2]) * 16) + (hex2int (mac_str [i * 2 + 1])); - + instance->u.atm_dev->esi[i] = + (hex2int(mac_str[i * 2]) * 16) + (hex2int(mac_str[i * 2 + 1])); + } /* First check whether the modem already seems to be alive */ ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), - 0x12, 0xc0, 0x07, 0x00, buf7, SIZE_7, HZ/2); + 0x12, 0xc0, 0x07, 0x00, buf7, SIZE_7, HZ / 2); if (ret == SIZE_7) { dbg("firmware appears to be already loaded"); speedtch_got_firmware(instance, 1); speedtch_poll_status(instance); } else { - speedtch_firmware_start (instance); + speedtch_firmware_start(instance); } - usb_set_intfdata (intf, instance); + usb_set_intfdata(intf, instance); return 0; -fail: - kfree (instance); + fail: + kfree(instance); return -ENOMEM; } -static void speedtch_usb_disconnect (struct usb_interface *intf) +static void speedtch_usb_disconnect(struct usb_interface *intf) { - struct speedtch_instance_data *instance = usb_get_intfdata (intf); + struct speedtch_instance_data *instance = usb_get_intfdata(intf); - dbg ("speedtch_usb_disconnect entered"); + dbg("speedtch_usb_disconnect entered"); if (!instance) { - dbg ("speedtch_usb_disconnect: NULL instance!"); + dbg("speedtch_usb_disconnect: NULL instance!"); return; } +/*QQ need to handle disconnects on interface #2 while uploading firmware */ +/*QQ and what about interface #1? */ + if (instance->int_urb) { struct urb *int_urb = instance->int_urb; instance->int_urb = NULL; @@ -847,33 +826,32 @@ static void speedtch_usb_disconnect (struct usb_interface *intf) udsl_instance_disconnect(&instance->u); /* clean up */ - usb_set_intfdata (intf, NULL); + usb_set_intfdata(intf, NULL); udsl_put_instance(&instance->u); } - /*********** ** init ** ***********/ -static int __init speedtch_usb_init (void) +static int __init speedtch_usb_init(void) { - dbg ("speedtch_usb_init: driver version " DRIVER_VERSION); + dbg("speedtch_usb_init: driver version " DRIVER_VERSION); - return usb_register (&speedtch_usb_driver); + return usb_register(&speedtch_usb_driver); } -static void __exit speedtch_usb_cleanup (void) +static void __exit speedtch_usb_cleanup(void) { - dbg ("speedtch_usb_cleanup entered"); + dbg("speedtch_usb_cleanup entered"); - usb_deregister (&speedtch_usb_driver); + usb_deregister(&speedtch_usb_driver); } -module_init (speedtch_usb_init); -module_exit (speedtch_usb_cleanup); +module_init(speedtch_usb_init); +module_exit(speedtch_usb_cleanup); -MODULE_AUTHOR (DRIVER_AUTHOR); -MODULE_DESCRIPTION (DRIVER_DESC); -MODULE_LICENSE ("GPL"); -MODULE_VERSION (DRIVER_VERSION); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRIVER_VERSION); diff --git a/drivers/usb/atm/usb_atm.c b/drivers/usb/atm/usb_atm.c index 9a792ab71e49..486458d86fe1 100644 --- a/drivers/usb/atm/usb_atm.c +++ b/drivers/usb/atm/usb_atm.c @@ -62,7 +62,6 @@ * */ - #include #include #include @@ -96,13 +95,13 @@ #include #ifdef DEBUG -#define DEBUG_ON(x) BUG_ON(x) +#define UDSL_ASSERT(x) BUG_ON(x) #else -#define DEBUG_ON(x) do { if (x); } while (0) +#define UDSL_ASSERT(x) #endif #ifdef VERBOSE_DEBUG -static int udsl_print_packet (const unsigned char *data, int len); +static int udsl_print_packet(const unsigned char *data, int len); #define PACKETDEBUG(arg...) udsl_print_packet (arg) #define vdbg(arg...) dbg (arg) #else @@ -121,72 +120,90 @@ static unsigned int num_snd_bufs = UDSL_DEFAULT_SND_BUFS; static unsigned int rcv_buf_size = UDSL_DEFAULT_RCV_BUF_SIZE; static unsigned int snd_buf_size = UDSL_DEFAULT_SND_BUF_SIZE; -module_param (num_rcv_urbs, uint, 0444); -MODULE_PARM_DESC (num_rcv_urbs, "Number of urbs used for reception (range: 0-" __MODULE_STRING (UDSL_MAX_RCV_URBS) ", default: " __MODULE_STRING (UDSL_DEFAULT_RCV_URBS) ")"); - -module_param (num_snd_urbs, uint, 0444); -MODULE_PARM_DESC (num_snd_urbs, "Number of urbs used for transmission (range: 0-" __MODULE_STRING (UDSL_MAX_SND_URBS) ", default: " __MODULE_STRING (UDSL_DEFAULT_SND_URBS) ")"); - -module_param (num_rcv_bufs, uint, 0444); -MODULE_PARM_DESC (num_rcv_bufs, "Number of buffers used for reception (range: 0-" __MODULE_STRING (UDSL_MAX_RCV_BUFS) ", default: " __MODULE_STRING (UDSL_DEFAULT_RCV_BUFS) ")"); - -module_param (num_snd_bufs, uint, 0444); -MODULE_PARM_DESC (num_snd_bufs, "Number of buffers used for transmission (range: 0-" __MODULE_STRING (UDSL_MAX_SND_BUFS) ", default: " __MODULE_STRING (UDSL_DEFAULT_SND_BUFS) ")"); - -module_param (rcv_buf_size, uint, 0444); -MODULE_PARM_DESC (rcv_buf_size, "Size of the buffers used for reception (range: 0-" __MODULE_STRING (UDSL_MAX_RCV_BUF_SIZE) ", default: " __MODULE_STRING (UDSL_DEFAULT_RCV_BUF_SIZE) ")"); - -module_param (snd_buf_size, uint, 0444); -MODULE_PARM_DESC (snd_buf_size, "Size of the buffers used for transmission (range: 0-" __MODULE_STRING (UDSL_MAX_SND_BUF_SIZE) ", default: " __MODULE_STRING (UDSL_DEFAULT_SND_BUF_SIZE) ")"); +module_param(num_rcv_urbs, uint, 0444); +MODULE_PARM_DESC(num_rcv_urbs, + "Number of urbs used for reception (range: 0-" + __MODULE_STRING(UDSL_MAX_RCV_URBS) ", default: " + __MODULE_STRING(UDSL_DEFAULT_RCV_URBS) ")"); + +module_param(num_snd_urbs, uint, 0444); +MODULE_PARM_DESC(num_snd_urbs, + "Number of urbs used for transmission (range: 0-" + __MODULE_STRING(UDSL_MAX_SND_URBS) ", default: " + __MODULE_STRING(UDSL_DEFAULT_SND_URBS) ")"); + +module_param(num_rcv_bufs, uint, 0444); +MODULE_PARM_DESC(num_rcv_bufs, + "Number of buffers used for reception (range: 0-" + __MODULE_STRING(UDSL_MAX_RCV_BUFS) ", default: " + __MODULE_STRING(UDSL_DEFAULT_RCV_BUFS) ")"); + +module_param(num_snd_bufs, uint, 0444); +MODULE_PARM_DESC(num_snd_bufs, + "Number of buffers used for transmission (range: 0-" + __MODULE_STRING(UDSL_MAX_SND_BUFS) ", default: " + __MODULE_STRING(UDSL_DEFAULT_SND_BUFS) ")"); + +module_param(rcv_buf_size, uint, 0444); +MODULE_PARM_DESC(rcv_buf_size, + "Size of the buffers used for reception (range: 0-" + __MODULE_STRING(UDSL_MAX_RCV_BUF_SIZE) ", default: " + __MODULE_STRING(UDSL_DEFAULT_RCV_BUF_SIZE) ")"); + +module_param(snd_buf_size, uint, 0444); +MODULE_PARM_DESC(snd_buf_size, + "Size of the buffers used for transmission (range: 0-" + __MODULE_STRING(UDSL_MAX_SND_BUF_SIZE) ", default: " + __MODULE_STRING(UDSL_DEFAULT_SND_BUF_SIZE) ")"); /* ATM */ -static void udsl_atm_dev_close (struct atm_dev *dev); -static int udsl_atm_open (struct atm_vcc *vcc); -static void udsl_atm_close (struct atm_vcc *vcc); -static int udsl_atm_ioctl (struct atm_dev *dev, unsigned int cmd, void __user *arg); -static int udsl_atm_send (struct atm_vcc *vcc, struct sk_buff *skb); -static int udsl_atm_proc_read (struct atm_dev *atm_dev, loff_t *pos, char *page); +static void udsl_atm_dev_close(struct atm_dev *dev); +static int udsl_atm_open(struct atm_vcc *vcc); +static void udsl_atm_close(struct atm_vcc *vcc); +static int udsl_atm_ioctl(struct atm_dev *dev, unsigned int cmd, void __user * arg); +static int udsl_atm_send(struct atm_vcc *vcc, struct sk_buff *skb); +static int udsl_atm_proc_read(struct atm_dev *atm_dev, loff_t * pos, char *page); static struct atmdev_ops udsl_atm_devops = { - .dev_close = udsl_atm_dev_close, - .open = udsl_atm_open, - .close = udsl_atm_close, - .ioctl = udsl_atm_ioctl, - .send = udsl_atm_send, - .proc_read = udsl_atm_proc_read, - .owner = THIS_MODULE, + .dev_close = udsl_atm_dev_close, + .open = udsl_atm_open, + .close = udsl_atm_close, + .ioctl = udsl_atm_ioctl, + .send = udsl_atm_send, + .proc_read = udsl_atm_proc_read, + .owner = THIS_MODULE, }; - /*********** ** misc ** ***********/ -static inline void udsl_pop (struct atm_vcc *vcc, struct sk_buff *skb) +static inline void udsl_pop(struct atm_vcc *vcc, struct sk_buff *skb) { if (vcc->pop) - vcc->pop (vcc, skb); + vcc->pop(vcc, skb); else - dev_kfree_skb (skb); + dev_kfree_skb(skb); } - /************* ** decode ** *************/ -static inline struct udsl_vcc_data *udsl_find_vcc (struct udsl_instance_data *instance, short vpi, int vci) +static inline struct udsl_vcc_data *udsl_find_vcc(struct udsl_instance_data *instance, + short vpi, int vci) { struct udsl_vcc_data *vcc; - list_for_each_entry (vcc, &instance->vcc_list, list) + list_for_each_entry(vcc, &instance->vcc_list, list) if ((vcc->vci == vci) && (vcc->vpi == vpi)) return vcc; return NULL; } -static void udsl_extract_cells (struct udsl_instance_data *instance, unsigned char *source, unsigned int howmany) +static void udsl_extract_cells(struct udsl_instance_data *instance, + unsigned char *source, unsigned int howmany) { struct udsl_vcc_data *cached_vcc = NULL; struct atm_vcc *vcc; @@ -199,21 +216,22 @@ static void udsl_extract_cells (struct udsl_instance_data *instance, unsigned ch short cached_vpi = 0; short vpi; - for (i = 0; i < howmany; i++, source += ATM_CELL_SIZE + instance->rcv_padding) { - vpi = ((source [0] & 0x0f) << 4) | (source [1] >> 4); - vci = ((source [1] & 0x0f) << 12) | (source [2] << 4) | (source [3] >> 4); - pti = (source [3] & 0x2) != 0; + for (i = 0; i < howmany; + i++, source += ATM_CELL_SIZE + instance->rcv_padding) { + vpi = ((source[0] & 0x0f) << 4) | (source[1] >> 4); + vci = ((source[1] & 0x0f) << 12) | (source[2] << 4) | (source[3] >> 4); + pti = (source[3] & 0x2) != 0; - vdbg ("udsl_extract_cells: vpi %hd, vci %d, pti %d", vpi, vci, pti); + vdbg("udsl_extract_cells: vpi %hd, vci %d, pti %d", vpi, vci, pti); if (cached_vcc && (vci == cached_vci) && (vpi == cached_vpi)) vcc_data = cached_vcc; - else if ((vcc_data = udsl_find_vcc (instance, vpi, vci))) { + else if ((vcc_data = udsl_find_vcc(instance, vpi, vci))) { cached_vcc = vcc_data; cached_vpi = vpi; cached_vci = vci; } else { - dbg ("udsl_extract_cells: unknown vpi/vci (%hd/%d)!", vpi, vci); + dbg("udsl_extract_cells: unknown vpi/vci (%hd/%d)!", vpi, vci); continue; } @@ -221,95 +239,94 @@ static void udsl_extract_cells (struct udsl_instance_data *instance, unsigned ch sarb = vcc_data->sarb; if (sarb->tail + ATM_CELL_PAYLOAD > sarb->end) { - dbg ("udsl_extract_cells: buffer overrun (sarb->len %u, vcc: 0x%p)!", sarb->len, vcc); + dbg("udsl_extract_cells: buffer overrun (sarb->len %u, vcc: 0x%p)!", sarb->len, vcc); /* discard cells already received */ - skb_trim (sarb, 0); + skb_trim(sarb, 0); } - memcpy (sarb->tail, source + ATM_CELL_HEADER, ATM_CELL_PAYLOAD); - __skb_put (sarb, ATM_CELL_PAYLOAD); + memcpy(sarb->tail, source + ATM_CELL_HEADER, ATM_CELL_PAYLOAD); + __skb_put(sarb, ATM_CELL_PAYLOAD); if (pti) { struct sk_buff *skb; unsigned int length; unsigned int pdu_length; - length = (source [ATM_CELL_SIZE - 6] << 8) + source [ATM_CELL_SIZE - 5]; + length = (source[ATM_CELL_SIZE - 6] << 8) + source[ATM_CELL_SIZE - 5]; /* guard against overflow */ if (length > ATM_MAX_AAL5_PDU) { - dbg ("udsl_extract_cells: bogus length %u (vcc: 0x%p)!", length, vcc); - atomic_inc (&vcc->stats->rx_err); + dbg("udsl_extract_cells: bogus length %u (vcc: 0x%p)!", length, vcc); + atomic_inc(&vcc->stats->rx_err); goto out; } - pdu_length = UDSL_NUM_CELLS (length) * ATM_CELL_PAYLOAD; + pdu_length = UDSL_NUM_CELLS(length) * ATM_CELL_PAYLOAD; if (sarb->len < pdu_length) { - dbg ("udsl_extract_cells: bogus pdu_length %u (sarb->len: %u, vcc: 0x%p)!", pdu_length, sarb->len, vcc); - atomic_inc (&vcc->stats->rx_err); + dbg("udsl_extract_cells: bogus pdu_length %u (sarb->len: %u, vcc: 0x%p)!", pdu_length, sarb->len, vcc); + atomic_inc(&vcc->stats->rx_err); goto out; } - if (crc32_be (~0, sarb->tail - pdu_length, pdu_length) != 0xc704dd7b) { - dbg ("udsl_extract_cells: packet failed crc check (vcc: 0x%p)!", vcc); - atomic_inc (&vcc->stats->rx_err); + if (crc32_be(~0, sarb->tail - pdu_length, pdu_length) != 0xc704dd7b) { + dbg("udsl_extract_cells: packet failed crc check (vcc: 0x%p)!", vcc); + atomic_inc(&vcc->stats->rx_err); goto out; } - vdbg ("udsl_extract_cells: got packet (length: %u, pdu_length: %u, vcc: 0x%p)", length, pdu_length, vcc); + vdbg("udsl_extract_cells: got packet (length: %u, pdu_length: %u, vcc: 0x%p)", length, pdu_length, vcc); - if (!(skb = dev_alloc_skb (length))) { - dbg ("udsl_extract_cells: no memory for skb (length: %u)!", length); - atomic_inc (&vcc->stats->rx_drop); + if (!(skb = dev_alloc_skb(length))) { + dbg("udsl_extract_cells: no memory for skb (length: %u)!", length); + atomic_inc(&vcc->stats->rx_drop); goto out; } - vdbg ("udsl_extract_cells: allocated new sk_buff (skb: 0x%p, skb->truesize: %u)", skb, skb->truesize); + vdbg("udsl_extract_cells: allocated new sk_buff (skb: 0x%p, skb->truesize: %u)", skb, skb->truesize); - if (!atm_charge (vcc, skb->truesize)) { - dbg ("udsl_extract_cells: failed atm_charge (skb->truesize: %u)!", skb->truesize); - dev_kfree_skb (skb); - goto out; /* atm_charge increments rx_drop */ + if (!atm_charge(vcc, skb->truesize)) { + dbg("udsl_extract_cells: failed atm_charge (skb->truesize: %u)!", skb->truesize); + dev_kfree_skb(skb); + goto out; /* atm_charge increments rx_drop */ } - memcpy (skb->data, sarb->tail - pdu_length, length); - __skb_put (skb, length); + memcpy(skb->data, sarb->tail - pdu_length, length); + __skb_put(skb, length); - vdbg ("udsl_extract_cells: sending skb 0x%p, skb->len %u, skb->truesize %u", skb, skb->len, skb->truesize); + vdbg("udsl_extract_cells: sending skb 0x%p, skb->len %u, skb->truesize %u", skb, skb->len, skb->truesize); - PACKETDEBUG (skb->data, skb->len); + PACKETDEBUG(skb->data, skb->len); - vcc->push (vcc, skb); + vcc->push(vcc, skb); - atomic_inc (&vcc->stats->rx); -out: - skb_trim (sarb, 0); + atomic_inc(&vcc->stats->rx); + out: + skb_trim(sarb, 0); } } } - /************* ** encode ** *************/ -static const unsigned char zeros [ATM_CELL_PAYLOAD]; +static const unsigned char zeros[ATM_CELL_PAYLOAD]; -static void udsl_groom_skb (struct atm_vcc *vcc, struct sk_buff *skb) +static void udsl_groom_skb(struct atm_vcc *vcc, struct sk_buff *skb) { - struct udsl_control *ctrl = UDSL_SKB (skb); + struct udsl_control *ctrl = UDSL_SKB(skb); unsigned int zero_padding; u32 crc; ctrl->atm_data.vcc = vcc; - ctrl->cell_header [0] = vcc->vpi >> 4; - ctrl->cell_header [1] = (vcc->vpi << 4) | (vcc->vci >> 12); - ctrl->cell_header [2] = vcc->vci >> 4; - ctrl->cell_header [3] = vcc->vci << 4; - ctrl->cell_header [4] = 0xec; + ctrl->cell_header[0] = vcc->vpi >> 4; + ctrl->cell_header[1] = (vcc->vpi << 4) | (vcc->vci >> 12); + ctrl->cell_header[2] = vcc->vci >> 4; + ctrl->cell_header[3] = vcc->vci << 4; + ctrl->cell_header[4] = 0xec; - ctrl->num_cells = UDSL_NUM_CELLS (skb->len); + ctrl->num_cells = UDSL_NUM_CELLS(skb->len); ctrl->num_entire = skb->len / ATM_CELL_PAYLOAD; zero_padding = ctrl->num_cells * ATM_CELL_PAYLOAD - skb->len - ATM_AAL5_TRAILER; @@ -319,44 +336,45 @@ static void udsl_groom_skb (struct atm_vcc *vcc, struct sk_buff *skb) else ctrl->pdu_padding = zero_padding; - ctrl->aal5_trailer [0] = 0; /* UU = 0 */ - ctrl->aal5_trailer [1] = 0; /* CPI = 0 */ - ctrl->aal5_trailer [2] = skb->len >> 8; - ctrl->aal5_trailer [3] = skb->len; + ctrl->aal5_trailer[0] = 0; /* UU = 0 */ + ctrl->aal5_trailer[1] = 0; /* CPI = 0 */ + ctrl->aal5_trailer[2] = skb->len >> 8; + ctrl->aal5_trailer[3] = skb->len; - crc = crc32_be (~0, skb->data, skb->len); - crc = crc32_be (crc, zeros, zero_padding); - crc = crc32_be (crc, ctrl->aal5_trailer, 4); + crc = crc32_be(~0, skb->data, skb->len); + crc = crc32_be(crc, zeros, zero_padding); + crc = crc32_be(crc, ctrl->aal5_trailer, 4); crc = ~crc; - ctrl->aal5_trailer [4] = crc >> 24; - ctrl->aal5_trailer [5] = crc >> 16; - ctrl->aal5_trailer [6] = crc >> 8; - ctrl->aal5_trailer [7] = crc; + ctrl->aal5_trailer[4] = crc >> 24; + ctrl->aal5_trailer[5] = crc >> 16; + ctrl->aal5_trailer[6] = crc >> 8; + ctrl->aal5_trailer[7] = crc; } -static unsigned int udsl_write_cells(struct udsl_instance_data *instance, unsigned int howmany, - struct sk_buff *skb, unsigned char **target_p) +static unsigned int udsl_write_cells(struct udsl_instance_data *instance, + unsigned int howmany, struct sk_buff *skb, + unsigned char **target_p) { - struct udsl_control *ctrl = UDSL_SKB (skb); + struct udsl_control *ctrl = UDSL_SKB(skb); unsigned char *target = *target_p; unsigned int nc, ne, i; - vdbg ("udsl_write_cells: howmany=%u, skb->len=%d, num_cells=%u, num_entire=%u, pdu_padding=%u", howmany, skb->len, ctrl->num_cells, ctrl->num_entire, ctrl->pdu_padding); + vdbg("udsl_write_cells: howmany=%u, skb->len=%d, num_cells=%u, num_entire=%u, pdu_padding=%u", howmany, skb->len, ctrl->num_cells, ctrl->num_entire, ctrl->pdu_padding); nc = ctrl->num_cells; - ne = min (howmany, ctrl->num_entire); + ne = min(howmany, ctrl->num_entire); for (i = 0; i < ne; i++) { - memcpy (target, ctrl->cell_header, ATM_CELL_HEADER); + memcpy(target, ctrl->cell_header, ATM_CELL_HEADER); target += ATM_CELL_HEADER; - memcpy (target, skb->data, ATM_CELL_PAYLOAD); + memcpy(target, skb->data, ATM_CELL_PAYLOAD); target += ATM_CELL_PAYLOAD; if (instance->snd_padding) { - memset (target, 0, instance->snd_padding); + memset(target, 0, instance->snd_padding); target += instance->snd_padding; } - __skb_pull (skb, ATM_CELL_PAYLOAD); + __skb_pull(skb, ATM_CELL_PAYLOAD); } ctrl->num_entire -= ne; @@ -365,15 +383,15 @@ static unsigned int udsl_write_cells(struct udsl_instance_data *instance, unsign goto out; if (instance->snd_padding) { - memset (target, 0, instance->snd_padding); + memset(target, 0, instance->snd_padding); target += instance->snd_padding; } - memcpy (target, ctrl->cell_header, ATM_CELL_HEADER); + memcpy(target, ctrl->cell_header, ATM_CELL_HEADER); target += ATM_CELL_HEADER; - memcpy (target, skb->data, skb->len); + memcpy(target, skb->data, skb->len); target += skb->len; - __skb_pull (skb, skb->len); - memset (target, 0, ctrl->pdu_padding); + __skb_pull(skb, skb->len); + memset(target, 0, ctrl->pdu_padding); target += ctrl->pdu_padding; if (--ctrl->num_cells) { @@ -382,33 +400,33 @@ static unsigned int udsl_write_cells(struct udsl_instance_data *instance, unsign goto out; } - memcpy (target, ctrl->cell_header, ATM_CELL_HEADER); + memcpy(target, ctrl->cell_header, ATM_CELL_HEADER); target += ATM_CELL_HEADER; - memset (target, 0, ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER); + memset(target, 0, ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER); target += ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER; - DEBUG_ON (--ctrl->num_cells); + --ctrl->num_cells; + UDSL_ASSERT(ctrl->num_cells); } - memcpy (target, ctrl->aal5_trailer, ATM_AAL5_TRAILER); + memcpy(target, ctrl->aal5_trailer, ATM_AAL5_TRAILER); target += ATM_AAL5_TRAILER; /* set pti bit in last cell */ *(target + 3 - ATM_CELL_SIZE) |= 0x2; if (instance->snd_padding) { - memset (target, 0, instance->snd_padding); + memset(target, 0, instance->snd_padding); target += instance->snd_padding; } -out: + out: *target_p = target; return nc - ctrl->num_cells; } - /************** ** receive ** **************/ -static void udsl_complete_receive (struct urb *urb, struct pt_regs *regs) +static void udsl_complete_receive(struct urb *urb, struct pt_regs *regs) { struct udsl_receive_buffer *buf; struct udsl_instance_data *instance; @@ -416,7 +434,7 @@ static void udsl_complete_receive (struct urb *urb, struct pt_regs *regs) unsigned long flags; if (!urb || !(rcv = urb->context)) { - dbg ("udsl_complete_receive: bad urb!"); + dbg("udsl_complete_receive: bad urb!"); return; } @@ -425,165 +443,172 @@ static void udsl_complete_receive (struct urb *urb, struct pt_regs *regs) buf->filled_cells = urb->actual_length / (ATM_CELL_SIZE + instance->rcv_padding); - vdbg ("udsl_complete_receive: urb 0x%p, status %d, actual_length %d, filled_cells %u, rcv 0x%p, buf 0x%p", urb, urb->status, urb->actual_length, buf->filled_cells, rcv, buf); + vdbg("udsl_complete_receive: urb 0x%p, status %d, actual_length %d, filled_cells %u, rcv 0x%p, buf 0x%p", urb, urb->status, urb->actual_length, buf->filled_cells, rcv, buf); - DEBUG_ON (buf->filled_cells > rcv_buf_size); + UDSL_ASSERT(buf->filled_cells > rcv_buf_size); /* may not be in_interrupt() */ - spin_lock_irqsave (&instance->receive_lock, flags); - list_add (&rcv->list, &instance->spare_receivers); - list_add_tail (&buf->list, &instance->filled_receive_buffers); - if (likely (!urb->status)) - tasklet_schedule (&instance->receive_tasklet); - spin_unlock_irqrestore (&instance->receive_lock, flags); + spin_lock_irqsave(&instance->receive_lock, flags); + list_add(&rcv->list, &instance->spare_receivers); + list_add_tail(&buf->list, &instance->filled_receive_buffers); + if (likely(!urb->status)) + tasklet_schedule(&instance->receive_tasklet); + spin_unlock_irqrestore(&instance->receive_lock, flags); } -static void udsl_process_receive (unsigned long data) +static void udsl_process_receive(unsigned long data) { struct udsl_receive_buffer *buf; - struct udsl_instance_data *instance = (struct udsl_instance_data *) data; + struct udsl_instance_data *instance = (struct udsl_instance_data *)data; struct udsl_receiver *rcv; int err; -made_progress: - while (!list_empty (&instance->spare_receive_buffers)) { - spin_lock_irq (&instance->receive_lock); - if (list_empty (&instance->spare_receivers)) { - spin_unlock_irq (&instance->receive_lock); + made_progress: + while (!list_empty(&instance->spare_receive_buffers)) { + spin_lock_irq(&instance->receive_lock); + if (list_empty(&instance->spare_receivers)) { + spin_unlock_irq(&instance->receive_lock); break; } - rcv = list_entry (instance->spare_receivers.next, struct udsl_receiver, list); - list_del (&rcv->list); - spin_unlock_irq (&instance->receive_lock); + rcv = list_entry(instance->spare_receivers.next, + struct udsl_receiver, list); + list_del(&rcv->list); + spin_unlock_irq(&instance->receive_lock); - buf = list_entry (instance->spare_receive_buffers.next, struct udsl_receive_buffer, list); - list_del (&buf->list); + buf = list_entry(instance->spare_receive_buffers.next, + struct udsl_receive_buffer, list); + list_del(&buf->list); rcv->buffer = buf; - usb_fill_bulk_urb (rcv->urb, - instance->usb_dev, - usb_rcvbulkpipe (instance->usb_dev, instance->data_endpoint), - buf->base, - rcv_buf_size * (ATM_CELL_SIZE + instance->rcv_padding), - udsl_complete_receive, - rcv); + usb_fill_bulk_urb(rcv->urb, instance->usb_dev, + usb_rcvbulkpipe(instance->usb_dev, instance->data_endpoint), + buf->base, + rcv_buf_size * (ATM_CELL_SIZE + instance->rcv_padding), + udsl_complete_receive, rcv); - vdbg ("udsl_process_receive: sending urb 0x%p, rcv 0x%p, buf 0x%p", rcv->urb, rcv, buf); + vdbg("udsl_process_receive: sending urb 0x%p, rcv 0x%p, buf 0x%p", + rcv->urb, rcv, buf); if ((err = usb_submit_urb(rcv->urb, GFP_ATOMIC)) < 0) { - dbg ("udsl_process_receive: urb submission failed (%d)!", err); - list_add (&buf->list, &instance->spare_receive_buffers); - spin_lock_irq (&instance->receive_lock); - list_add (&rcv->list, &instance->spare_receivers); - spin_unlock_irq (&instance->receive_lock); + dbg("udsl_process_receive: urb submission failed (%d)!", err); + list_add(&buf->list, &instance->spare_receive_buffers); + spin_lock_irq(&instance->receive_lock); + list_add(&rcv->list, &instance->spare_receivers); + spin_unlock_irq(&instance->receive_lock); break; } } - spin_lock_irq (&instance->receive_lock); - if (list_empty (&instance->filled_receive_buffers)) { - spin_unlock_irq (&instance->receive_lock); - return; /* done - no more buffers */ + spin_lock_irq(&instance->receive_lock); + if (list_empty(&instance->filled_receive_buffers)) { + spin_unlock_irq(&instance->receive_lock); + return; /* done - no more buffers */ } - buf = list_entry (instance->filled_receive_buffers.next, struct udsl_receive_buffer, list); - list_del (&buf->list); - spin_unlock_irq (&instance->receive_lock); - vdbg ("udsl_process_receive: processing buf 0x%p", buf); - udsl_extract_cells (instance, buf->base, buf->filled_cells); - list_add (&buf->list, &instance->spare_receive_buffers); + buf = list_entry(instance->filled_receive_buffers.next, + struct udsl_receive_buffer, list); + list_del(&buf->list); + spin_unlock_irq(&instance->receive_lock); + + vdbg("udsl_process_receive: processing buf 0x%p", buf); + udsl_extract_cells(instance, buf->base, buf->filled_cells); + list_add(&buf->list, &instance->spare_receive_buffers); goto made_progress; } - /*********** ** send ** ***********/ -static void udsl_complete_send (struct urb *urb, struct pt_regs *regs) +static void udsl_complete_send(struct urb *urb, struct pt_regs *regs) { struct udsl_instance_data *instance; struct udsl_sender *snd; unsigned long flags; if (!urb || !(snd = urb->context) || !(instance = snd->instance)) { - dbg ("udsl_complete_send: bad urb!"); + dbg("udsl_complete_send: bad urb!"); return; } - vdbg ("udsl_complete_send: urb 0x%p, status %d, snd 0x%p, buf 0x%p", urb, urb->status, snd, snd->buffer); + vdbg("udsl_complete_send: urb 0x%p, status %d, snd 0x%p, buf 0x%p", urb, + urb->status, snd, snd->buffer); /* may not be in_interrupt() */ - spin_lock_irqsave (&instance->send_lock, flags); - list_add (&snd->list, &instance->spare_senders); - list_add (&snd->buffer->list, &instance->spare_send_buffers); - tasklet_schedule (&instance->send_tasklet); - spin_unlock_irqrestore (&instance->send_lock, flags); + spin_lock_irqsave(&instance->send_lock, flags); + list_add(&snd->list, &instance->spare_senders); + list_add(&snd->buffer->list, &instance->spare_send_buffers); + tasklet_schedule(&instance->send_tasklet); + spin_unlock_irqrestore(&instance->send_lock, flags); } -static void udsl_process_send (unsigned long data) +static void udsl_process_send(unsigned long data) { struct udsl_send_buffer *buf; - struct udsl_instance_data *instance = (struct udsl_instance_data *) data; + struct udsl_instance_data *instance = (struct udsl_instance_data *)data; struct sk_buff *skb; struct udsl_sender *snd; int err; unsigned int num_written; -made_progress: - spin_lock_irq (&instance->send_lock); - while (!list_empty (&instance->spare_senders)) { - if (!list_empty (&instance->filled_send_buffers)) { - buf = list_entry (instance->filled_send_buffers.next, struct udsl_send_buffer, list); - list_del (&buf->list); + made_progress: + spin_lock_irq(&instance->send_lock); + while (!list_empty(&instance->spare_senders)) { + if (!list_empty(&instance->filled_send_buffers)) { + buf = list_entry(instance->filled_send_buffers.next, + struct udsl_send_buffer, list); + list_del(&buf->list); } else if ((buf = instance->current_buffer)) { instance->current_buffer = NULL; - } else /* all buffers empty */ + } else /* all buffers empty */ break; - snd = list_entry (instance->spare_senders.next, struct udsl_sender, list); - list_del (&snd->list); - spin_unlock_irq (&instance->send_lock); + snd = list_entry(instance->spare_senders.next, + struct udsl_sender, list); + list_del(&snd->list); + spin_unlock_irq(&instance->send_lock); snd->buffer = buf; - usb_fill_bulk_urb (snd->urb, - instance->usb_dev, - usb_sndbulkpipe (instance->usb_dev, instance->data_endpoint), - buf->base, - (snd_buf_size - buf->free_cells) * (ATM_CELL_SIZE + instance->snd_padding), - udsl_complete_send, - snd); + usb_fill_bulk_urb(snd->urb, instance->usb_dev, + usb_sndbulkpipe(instance->usb_dev, instance->data_endpoint), + buf->base, + (snd_buf_size - buf->free_cells) * (ATM_CELL_SIZE + instance->snd_padding), + udsl_complete_send, snd); - vdbg ("udsl_process_send: submitting urb 0x%p (%d cells), snd 0x%p, buf 0x%p", snd->urb, snd_buf_size - buf->free_cells, snd, buf); + vdbg("udsl_process_send: submitting urb 0x%p (%d cells), snd 0x%p, buf 0x%p", + snd->urb, snd_buf_size - buf->free_cells, snd, buf); if ((err = usb_submit_urb(snd->urb, GFP_ATOMIC)) < 0) { - dbg ("udsl_process_send: urb submission failed (%d)!", err); - spin_lock_irq (&instance->send_lock); - list_add (&snd->list, &instance->spare_senders); - spin_unlock_irq (&instance->send_lock); - list_add (&buf->list, &instance->filled_send_buffers); - return; /* bail out */ + dbg("udsl_process_send: urb submission failed (%d)!", err); + spin_lock_irq(&instance->send_lock); + list_add(&snd->list, &instance->spare_senders); + spin_unlock_irq(&instance->send_lock); + list_add(&buf->list, &instance->filled_send_buffers); + return; /* bail out */ } - spin_lock_irq (&instance->send_lock); - } /* while */ - spin_unlock_irq (&instance->send_lock); + spin_lock_irq(&instance->send_lock); + } /* while */ + spin_unlock_irq(&instance->send_lock); - if (!instance->current_skb && !(instance->current_skb = skb_dequeue (&instance->sndqueue))) - return; /* done - no more skbs */ + if (!instance->current_skb) + instance->current_skb = skb_dequeue(&instance->sndqueue); + if (!instance->current_skb) + return; /* done - no more skbs */ skb = instance->current_skb; if (!(buf = instance->current_buffer)) { - spin_lock_irq (&instance->send_lock); - if (list_empty (&instance->spare_send_buffers)) { + spin_lock_irq(&instance->send_lock); + if (list_empty(&instance->spare_send_buffers)) { instance->current_buffer = NULL; - spin_unlock_irq (&instance->send_lock); - return; /* done - no more buffers */ + spin_unlock_irq(&instance->send_lock); + return; /* done - no more buffers */ } - buf = list_entry (instance->spare_send_buffers.next, struct udsl_send_buffer, list); - list_del (&buf->list); - spin_unlock_irq (&instance->send_lock); + buf = list_entry(instance->spare_send_buffers.next, + struct udsl_send_buffer, list); + list_del(&buf->list); + spin_unlock_irq(&instance->send_lock); buf->free_start = buf->base; buf->free_cells = snd_buf_size; @@ -593,186 +618,191 @@ made_progress: num_written = udsl_write_cells(instance, buf->free_cells, skb, &buf->free_start); - vdbg ("udsl_process_send: wrote %u cells from skb 0x%p to buffer 0x%p", num_written, skb, buf); + vdbg("udsl_process_send: wrote %u cells from skb 0x%p to buffer 0x%p", + num_written, skb, buf); if (!(buf->free_cells -= num_written)) { - list_add_tail (&buf->list, &instance->filled_send_buffers); + list_add_tail(&buf->list, &instance->filled_send_buffers); instance->current_buffer = NULL; } - vdbg ("udsl_process_send: buffer contains %d cells, %d left", snd_buf_size - buf->free_cells, buf->free_cells); + vdbg("udsl_process_send: buffer contains %d cells, %d left", + snd_buf_size - buf->free_cells, buf->free_cells); - if (!UDSL_SKB (skb)->num_cells) { - struct atm_vcc *vcc = UDSL_SKB (skb)->atm_data.vcc; + if (!UDSL_SKB(skb)->num_cells) { + struct atm_vcc *vcc = UDSL_SKB(skb)->atm_data.vcc; - udsl_pop (vcc, skb); + udsl_pop(vcc, skb); instance->current_skb = NULL; - atomic_inc (&vcc->stats->tx); + atomic_inc(&vcc->stats->tx); } goto made_progress; } -static void udsl_cancel_send (struct udsl_instance_data *instance, struct atm_vcc *vcc) +static void udsl_cancel_send(struct udsl_instance_data *instance, + struct atm_vcc *vcc) { struct sk_buff *skb, *n; - dbg ("udsl_cancel_send entered"); - spin_lock_irq (&instance->sndqueue.lock); - for (skb = instance->sndqueue.next, n = skb->next; skb != (struct sk_buff *)&instance->sndqueue; skb = n, n = skb->next) - if (UDSL_SKB (skb)->atm_data.vcc == vcc) { - dbg ("udsl_cancel_send: popping skb 0x%p", skb); - __skb_unlink (skb, &instance->sndqueue); - udsl_pop (vcc, skb); + dbg("udsl_cancel_send entered"); + spin_lock_irq(&instance->sndqueue.lock); + for (skb = instance->sndqueue.next, n = skb->next; + skb != (struct sk_buff *)&instance->sndqueue; + skb = n, n = skb->next) + if (UDSL_SKB(skb)->atm_data.vcc == vcc) { + dbg("udsl_cancel_send: popping skb 0x%p", skb); + __skb_unlink(skb, &instance->sndqueue); + udsl_pop(vcc, skb); } - spin_unlock_irq (&instance->sndqueue.lock); + spin_unlock_irq(&instance->sndqueue.lock); - tasklet_disable (&instance->send_tasklet); - if ((skb = instance->current_skb) && (UDSL_SKB (skb)->atm_data.vcc == vcc)) { - dbg ("udsl_cancel_send: popping current skb (0x%p)", skb); + tasklet_disable(&instance->send_tasklet); + if ((skb = instance->current_skb) && (UDSL_SKB(skb)->atm_data.vcc == vcc)) { + dbg("udsl_cancel_send: popping current skb (0x%p)", skb); instance->current_skb = NULL; - udsl_pop (vcc, skb); + udsl_pop(vcc, skb); } - tasklet_enable (&instance->send_tasklet); - dbg ("udsl_cancel_send done"); + tasklet_enable(&instance->send_tasklet); + dbg("udsl_cancel_send done"); } -static int udsl_atm_send (struct atm_vcc *vcc, struct sk_buff *skb) +static int udsl_atm_send(struct atm_vcc *vcc, struct sk_buff *skb) { struct udsl_instance_data *instance = vcc->dev->dev_data; int err; - vdbg ("udsl_atm_send called (skb 0x%p, len %u)", skb, skb->len); + vdbg("udsl_atm_send called (skb 0x%p, len %u)", skb, skb->len); if (!instance) { - dbg ("udsl_atm_send: NULL data!"); + dbg("udsl_atm_send: NULL data!"); err = -ENODEV; goto fail; } if (vcc->qos.aal != ATM_AAL5) { - dbg ("udsl_atm_send: unsupported ATM type %d!", vcc->qos.aal); + dbg("udsl_atm_send: unsupported ATM type %d!", vcc->qos.aal); err = -EINVAL; goto fail; } if (skb->len > ATM_MAX_AAL5_PDU) { - dbg ("udsl_atm_send: packet too long (%d vs %d)!", skb->len, ATM_MAX_AAL5_PDU); + dbg("udsl_atm_send: packet too long (%d vs %d)!", skb->len, + ATM_MAX_AAL5_PDU); err = -EINVAL; goto fail; } - PACKETDEBUG (skb->data, skb->len); + PACKETDEBUG(skb->data, skb->len); - udsl_groom_skb (vcc, skb); - skb_queue_tail (&instance->sndqueue, skb); - tasklet_schedule (&instance->send_tasklet); + udsl_groom_skb(vcc, skb); + skb_queue_tail(&instance->sndqueue, skb); + tasklet_schedule(&instance->send_tasklet); return 0; -fail: - udsl_pop (vcc, skb); + fail: + udsl_pop(vcc, skb); return err; } - /******************** ** bean counting ** ********************/ static void udsl_destroy_instance(struct kref *kref) { - struct udsl_instance_data *instance = container_of (kref, struct udsl_instance_data, refcount); + struct udsl_instance_data *instance = + container_of(kref, struct udsl_instance_data, refcount); - tasklet_kill (&instance->receive_tasklet); - tasklet_kill (&instance->send_tasklet); - usb_put_dev (instance->usb_dev); - kfree (instance); + tasklet_kill(&instance->receive_tasklet); + tasklet_kill(&instance->send_tasklet); + usb_put_dev(instance->usb_dev); + kfree(instance); } - - void udsl_get_instance(struct udsl_instance_data *instance) { - kref_get (&instance->refcount); + kref_get(&instance->refcount); } void udsl_put_instance(struct udsl_instance_data *instance) { - kref_put (&instance->refcount, udsl_destroy_instance); + kref_put(&instance->refcount, udsl_destroy_instance); } - /********** ** ATM ** **********/ -static void udsl_atm_dev_close (struct atm_dev *dev) +static void udsl_atm_dev_close(struct atm_dev *dev) { struct udsl_instance_data *instance = dev->dev_data; dev->dev_data = NULL; - udsl_put_instance (instance); + udsl_put_instance(instance); } -static int udsl_atm_proc_read (struct atm_dev *atm_dev, loff_t *pos, char *page) +static int udsl_atm_proc_read(struct atm_dev *atm_dev, loff_t * pos, char *page) { struct udsl_instance_data *instance = atm_dev->dev_data; int left = *pos; if (!instance) { - dbg ("udsl_atm_proc_read: NULL instance!"); + dbg("udsl_atm_proc_read: NULL instance!"); return -ENODEV; } if (!left--) - return sprintf (page, "%s\n", instance->description); + return sprintf(page, "%s\n", instance->description); if (!left--) - return sprintf (page, "MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", - atm_dev->esi [0], atm_dev->esi [1], atm_dev->esi [2], - atm_dev->esi [3], atm_dev->esi [4], atm_dev->esi [5]); + return sprintf(page, "MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", + atm_dev->esi[0], atm_dev->esi[1], + atm_dev->esi[2], atm_dev->esi[3], + atm_dev->esi[4], atm_dev->esi[5]); if (!left--) - return sprintf (page, "AAL5: tx %d ( %d err ), rx %d ( %d err, %d drop )\n", - atomic_read (&atm_dev->stats.aal5.tx), - atomic_read (&atm_dev->stats.aal5.tx_err), - atomic_read (&atm_dev->stats.aal5.rx), - atomic_read (&atm_dev->stats.aal5.rx_err), - atomic_read (&atm_dev->stats.aal5.rx_drop)); + return sprintf(page, + "AAL5: tx %d ( %d err ), rx %d ( %d err, %d drop )\n", + atomic_read(&atm_dev->stats.aal5.tx), + atomic_read(&atm_dev->stats.aal5.tx_err), + atomic_read(&atm_dev->stats.aal5.rx), + atomic_read(&atm_dev->stats.aal5.rx_err), + atomic_read(&atm_dev->stats.aal5.rx_drop)); if (!left--) { switch (atm_dev->signal) { case ATM_PHY_SIG_FOUND: - sprintf (page, "Line up"); + sprintf(page, "Line up"); break; case ATM_PHY_SIG_LOST: - sprintf (page, "Line down"); + sprintf(page, "Line down"); break; default: - sprintf (page, "Line state unknown"); + sprintf(page, "Line state unknown"); break; } if (instance->usb_dev->state == USB_STATE_NOTATTACHED) - strcat (page, ", disconnected\n"); + strcat(page, ", disconnected\n"); else { if (instance->status == UDSL_LOADED_FIRMWARE) - strcat (page, ", firmware loaded\n"); + strcat(page, ", firmware loaded\n"); else if (instance->status == UDSL_LOADING_FIRMWARE) - strcat (page, ", firmware loading\n"); + strcat(page, ", firmware loading\n"); else - strcat (page, ", no firmware\n"); + strcat(page, ", no firmware\n"); } - return strlen (page); + return strlen(page); } return 0; } -static int udsl_atm_open (struct atm_vcc *vcc) +static int udsl_atm_open(struct atm_vcc *vcc) { struct udsl_instance_data *instance = vcc->dev->dev_data; struct udsl_vcc_data *new; @@ -781,213 +811,223 @@ static int udsl_atm_open (struct atm_vcc *vcc) short vpi = vcc->vpi; int err; - dbg ("udsl_atm_open: vpi %hd, vci %d", vpi, vci); + dbg("udsl_atm_open: vpi %hd, vci %d", vpi, vci); if (!instance) { - dbg ("udsl_atm_open: NULL data!"); + dbg("udsl_atm_open: NULL data!"); return -ENODEV; } /* only support AAL5 */ - if ((vcc->qos.aal != ATM_AAL5) || (vcc->qos.rxtp.max_sdu < 0) || (vcc->qos.rxtp.max_sdu > ATM_MAX_AAL5_PDU)) { - dbg ("udsl_atm_open: unsupported ATM type %d!", vcc->qos.aal); + if ((vcc->qos.aal != ATM_AAL5) || (vcc->qos.rxtp.max_sdu < 0) + || (vcc->qos.rxtp.max_sdu > ATM_MAX_AAL5_PDU)) { + dbg("udsl_atm_open: unsupported ATM type %d!", vcc->qos.aal); return -EINVAL; } if (instance->firmware_wait && - (err = instance->firmware_wait (instance)) < 0) { - dbg ("udsl_atm_open: firmware not loaded (%d)!", err); + (err = instance->firmware_wait(instance)) < 0) { + dbg("udsl_atm_open: firmware not loaded (%d)!", err); return err; } - down (&instance->serialize); /* vs self, udsl_atm_close */ + down(&instance->serialize); /* vs self, udsl_atm_close */ - if (udsl_find_vcc (instance, vpi, vci)) { - dbg ("udsl_atm_open: %hd/%d already in use!", vpi, vci); - up (&instance->serialize); + if (udsl_find_vcc(instance, vpi, vci)) { + dbg("udsl_atm_open: %hd/%d already in use!", vpi, vci); + up(&instance->serialize); return -EADDRINUSE; } - if (!(new = kmalloc (sizeof (struct udsl_vcc_data), GFP_KERNEL))) { - dbg ("udsl_atm_open: no memory for vcc_data!"); - up (&instance->serialize); + if (!(new = kmalloc(sizeof(struct udsl_vcc_data), GFP_KERNEL))) { + dbg("udsl_atm_open: no memory for vcc_data!"); + up(&instance->serialize); return -ENOMEM; } - memset (new, 0, sizeof (struct udsl_vcc_data)); + memset(new, 0, sizeof(struct udsl_vcc_data)); new->vcc = vcc; new->vpi = vpi; new->vci = vci; /* udsl_extract_cells requires at least one cell */ - max_pdu = max (1, UDSL_NUM_CELLS (vcc->qos.rxtp.max_sdu)) * ATM_CELL_PAYLOAD; - if (!(new->sarb = alloc_skb (max_pdu, GFP_KERNEL))) { - dbg ("udsl_atm_open: no memory for SAR buffer!"); - kfree (new); - up (&instance->serialize); + max_pdu = max(1, UDSL_NUM_CELLS(vcc->qos.rxtp.max_sdu)) * ATM_CELL_PAYLOAD; + if (!(new->sarb = alloc_skb(max_pdu, GFP_KERNEL))) { + dbg("udsl_atm_open: no memory for SAR buffer!"); + kfree(new); + up(&instance->serialize); return -ENOMEM; } vcc->dev_data = new; - tasklet_disable (&instance->receive_tasklet); - list_add (&new->list, &instance->vcc_list); - tasklet_enable (&instance->receive_tasklet); + tasklet_disable(&instance->receive_tasklet); + list_add(&new->list, &instance->vcc_list); + tasklet_enable(&instance->receive_tasklet); - set_bit (ATM_VF_ADDR, &vcc->flags); - set_bit (ATM_VF_PARTIAL, &vcc->flags); - set_bit (ATM_VF_READY, &vcc->flags); + set_bit(ATM_VF_ADDR, &vcc->flags); + set_bit(ATM_VF_PARTIAL, &vcc->flags); + set_bit(ATM_VF_READY, &vcc->flags); - up (&instance->serialize); + up(&instance->serialize); - tasklet_schedule (&instance->receive_tasklet); + tasklet_schedule(&instance->receive_tasklet); - dbg ("udsl_atm_open: allocated vcc data 0x%p (max_pdu: %u)", new, max_pdu); + dbg("udsl_atm_open: allocated vcc data 0x%p (max_pdu: %u)", new, max_pdu); return 0; } -static void udsl_atm_close (struct atm_vcc *vcc) +static void udsl_atm_close(struct atm_vcc *vcc) { struct udsl_instance_data *instance = vcc->dev->dev_data; struct udsl_vcc_data *vcc_data = vcc->dev_data; - dbg ("udsl_atm_close called"); + dbg("udsl_atm_close called"); if (!instance || !vcc_data) { - dbg ("udsl_atm_close: NULL data!"); + dbg("udsl_atm_close: NULL data!"); return; } - dbg ("udsl_atm_close: deallocating vcc 0x%p with vpi %d vci %d", vcc_data, vcc_data->vpi, vcc_data->vci); + dbg("udsl_atm_close: deallocating vcc 0x%p with vpi %d vci %d", + vcc_data, vcc_data->vpi, vcc_data->vci); - udsl_cancel_send (instance, vcc); + udsl_cancel_send(instance, vcc); - down (&instance->serialize); /* vs self, udsl_atm_open */ + down(&instance->serialize); /* vs self, udsl_atm_open */ - tasklet_disable (&instance->receive_tasklet); - list_del (&vcc_data->list); - tasklet_enable (&instance->receive_tasklet); + tasklet_disable(&instance->receive_tasklet); + list_del(&vcc_data->list); + tasklet_enable(&instance->receive_tasklet); - kfree_skb (vcc_data->sarb); + kfree_skb(vcc_data->sarb); vcc_data->sarb = NULL; - kfree (vcc_data); + kfree(vcc_data); vcc->dev_data = NULL; vcc->vpi = ATM_VPI_UNSPEC; vcc->vci = ATM_VCI_UNSPEC; - clear_bit (ATM_VF_READY, &vcc->flags); - clear_bit (ATM_VF_PARTIAL, &vcc->flags); - clear_bit (ATM_VF_ADDR, &vcc->flags); + clear_bit(ATM_VF_READY, &vcc->flags); + clear_bit(ATM_VF_PARTIAL, &vcc->flags); + clear_bit(ATM_VF_ADDR, &vcc->flags); - up (&instance->serialize); + up(&instance->serialize); - dbg ("udsl_atm_close successful"); + dbg("udsl_atm_close successful"); } -static int udsl_atm_ioctl (struct atm_dev *dev, unsigned int cmd, void __user *arg) +static int udsl_atm_ioctl(struct atm_dev *dev, unsigned int cmd, + void __user * arg) { switch (cmd) { case ATM_QUERYLOOP: - return put_user (ATM_LM_NONE, (int __user *)arg) ? -EFAULT : 0; + return put_user(ATM_LM_NONE, (int __user *)arg) ? -EFAULT : 0; default: return -ENOIOCTLCMD; } } - /********** ** USB ** **********/ -int udsl_instance_setup(struct usb_device *dev, struct udsl_instance_data *instance) +int udsl_instance_setup(struct usb_device *dev, + struct udsl_instance_data *instance) { char *buf; int i, length; - kref_init (&instance->refcount); /* one for USB */ - udsl_get_instance (instance); /* one for ATM */ + kref_init(&instance->refcount); /* one for USB */ + udsl_get_instance(instance); /* one for ATM */ - - init_MUTEX (&instance->serialize); + init_MUTEX(&instance->serialize); instance->usb_dev = dev; - INIT_LIST_HEAD (&instance->vcc_list); + INIT_LIST_HEAD(&instance->vcc_list); instance->status = UDSL_NO_FIRMWARE; - init_waitqueue_head (&instance->firmware_waiters); + init_waitqueue_head(&instance->firmware_waiters); - spin_lock_init (&instance->receive_lock); - INIT_LIST_HEAD (&instance->spare_receivers); - INIT_LIST_HEAD (&instance->filled_receive_buffers); + spin_lock_init(&instance->receive_lock); + INIT_LIST_HEAD(&instance->spare_receivers); + INIT_LIST_HEAD(&instance->filled_receive_buffers); - tasklet_init (&instance->receive_tasklet, udsl_process_receive, (unsigned long) instance); - INIT_LIST_HEAD (&instance->spare_receive_buffers); + tasklet_init(&instance->receive_tasklet, udsl_process_receive, (unsigned long)instance); + INIT_LIST_HEAD(&instance->spare_receive_buffers); - skb_queue_head_init (&instance->sndqueue); + skb_queue_head_init(&instance->sndqueue); - spin_lock_init (&instance->send_lock); - INIT_LIST_HEAD (&instance->spare_senders); - INIT_LIST_HEAD (&instance->spare_send_buffers); + spin_lock_init(&instance->send_lock); + INIT_LIST_HEAD(&instance->spare_senders); + INIT_LIST_HEAD(&instance->spare_send_buffers); - tasklet_init (&instance->send_tasklet, udsl_process_send, (unsigned long) instance); - INIT_LIST_HEAD (&instance->filled_send_buffers); + tasklet_init(&instance->send_tasklet, udsl_process_send, + (unsigned long)instance); + INIT_LIST_HEAD(&instance->filled_send_buffers); /* receive init */ for (i = 0; i < num_rcv_urbs; i++) { - struct udsl_receiver *rcv = &(instance->receivers [i]); + struct udsl_receiver *rcv = &(instance->receivers[i]); - if (!(rcv->urb = usb_alloc_urb (0, GFP_KERNEL))) { - dbg ("udsl_usb_probe: no memory for receive urb %d!", i); + if (!(rcv->urb = usb_alloc_urb(0, GFP_KERNEL))) { + dbg("udsl_usb_probe: no memory for receive urb %d!", i); goto fail; } rcv->instance = instance; - list_add (&rcv->list, &instance->spare_receivers); + list_add(&rcv->list, &instance->spare_receivers); } for (i = 0; i < num_rcv_bufs; i++) { - struct udsl_receive_buffer *buf = &(instance->receive_buffers [i]); + struct udsl_receive_buffer *buf = + &(instance->receive_buffers[i]); - if (!(buf->base = kmalloc (rcv_buf_size * (ATM_CELL_SIZE + instance->rcv_padding), GFP_KERNEL))) { - dbg ("udsl_usb_probe: no memory for receive buffer %d!", i); + buf->base = kmalloc(rcv_buf_size * (ATM_CELL_SIZE + instance->rcv_padding), + GFP_KERNEL); + if (!buf->base) { + dbg("udsl_usb_probe: no memory for receive buffer %d!", i); goto fail; } - list_add (&buf->list, &instance->spare_receive_buffers); + list_add(&buf->list, &instance->spare_receive_buffers); } /* send init */ for (i = 0; i < num_snd_urbs; i++) { - struct udsl_sender *snd = &(instance->senders [i]); + struct udsl_sender *snd = &(instance->senders[i]); - if (!(snd->urb = usb_alloc_urb (0, GFP_KERNEL))) { - dbg ("udsl_usb_probe: no memory for send urb %d!", i); + if (!(snd->urb = usb_alloc_urb(0, GFP_KERNEL))) { + dbg("udsl_usb_probe: no memory for send urb %d!", i); goto fail; } snd->instance = instance; - list_add (&snd->list, &instance->spare_senders); + list_add(&snd->list, &instance->spare_senders); } for (i = 0; i < num_snd_bufs; i++) { - struct udsl_send_buffer *buf = &(instance->send_buffers [i]); + struct udsl_send_buffer *buf = &(instance->send_buffers[i]); - if (!(buf->base = kmalloc (snd_buf_size * (ATM_CELL_SIZE + instance->snd_padding), GFP_KERNEL))) { - dbg ("udsl_usb_probe: no memory for send buffer %d!", i); + buf->base = kmalloc(snd_buf_size * (ATM_CELL_SIZE + instance->snd_padding), + GFP_KERNEL); + if (!buf->base) { + dbg("udsl_usb_probe: no memory for send buffer %d!", i); goto fail; } - list_add (&buf->list, &instance->spare_send_buffers); + list_add(&buf->list, &instance->spare_send_buffers); } /* ATM init */ - if (!(instance->atm_dev = atm_dev_register (instance->driver_name, &udsl_atm_devops, -1, NULL))) { - dbg ("udsl_usb_probe: failed to register ATM device!"); + instance->atm_dev = atm_dev_register(instance->driver_name, + &udsl_atm_devops, -1, NULL); + if (!instance->atm_dev) { + dbg("udsl_usb_probe: failed to register ATM device!"); goto fail; } @@ -1000,139 +1040,101 @@ int udsl_instance_setup(struct usb_device *dev, struct udsl_instance_data *insta /* device description */ buf = instance->description; - length = sizeof (instance->description); + length = sizeof(instance->description); - if ((i = usb_string (dev, dev->descriptor.iProduct, buf, length)) < 0) + if ((i = usb_string(dev, dev->descriptor.iProduct, buf, length)) < 0) goto finish; buf += i; length -= i; - i = scnprintf (buf, length, " ("); + i = scnprintf(buf, length, " ("); buf += i; length -= i; - if (length <= 0 || (i = usb_make_path (dev, buf, length)) < 0) + if (length <= 0 || (i = usb_make_path(dev, buf, length)) < 0) goto finish; buf += i; length -= i; - snprintf (buf, length, ")"); + snprintf(buf, length, ")"); -finish: + finish: /* ready for ATM callbacks */ - wmb (); + wmb(); instance->atm_dev->dev_data = instance; - usb_get_dev (dev); + usb_get_dev(dev); return 0; -fail: + fail: for (i = 0; i < num_snd_bufs; i++) - kfree (instance->send_buffers [i].base); + kfree(instance->send_buffers[i].base); for (i = 0; i < num_snd_urbs; i++) - usb_free_urb (instance->senders [i].urb); + usb_free_urb(instance->senders[i].urb); for (i = 0; i < num_rcv_bufs; i++) - kfree (instance->receive_buffers [i].base); + kfree(instance->receive_buffers[i].base); for (i = 0; i < num_rcv_urbs; i++) - usb_free_urb (instance->receivers [i].urb); + usb_free_urb(instance->receivers[i].urb); return -ENOMEM; } -void udsl_instance_disconnect (struct udsl_instance_data *instance) +void udsl_instance_disconnect(struct udsl_instance_data *instance) { - struct list_head *pos; - unsigned int count; - int result, i; + int i; - dbg ("udsl_instance_disconnect entered"); + dbg("udsl_instance_disconnect entered"); if (!instance) { - dbg ("udsl_instance_disconnect: NULL instance!"); + dbg("udsl_instance_disconnect: NULL instance!"); return; } /* receive finalize */ - tasklet_disable (&instance->receive_tasklet); + tasklet_disable(&instance->receive_tasklet); for (i = 0; i < num_rcv_urbs; i++) - if ((result = usb_unlink_urb (instance->receivers [i].urb)) < 0) - dbg ("udsl_instance_disconnect: usb_unlink_urb on receive urb %d returned %d!", i, result); - - /* wait for completion handlers to finish */ - do { - count = 0; - spin_lock_irq (&instance->receive_lock); - list_for_each (pos, &instance->spare_receivers) - DEBUG_ON (++count > num_rcv_urbs); - spin_unlock_irq (&instance->receive_lock); - - dbg ("udsl_instance_disconnect: found %u spare receivers", count); - - if (count == num_rcv_urbs) - break; - - set_current_state (TASK_RUNNING); - schedule (); - } while (1); + usb_kill_urb(instance->receivers[i].urb); /* no need to take the spinlock */ - INIT_LIST_HEAD (&instance->filled_receive_buffers); - INIT_LIST_HEAD (&instance->spare_receive_buffers); + INIT_LIST_HEAD(&instance->filled_receive_buffers); + INIT_LIST_HEAD(&instance->spare_receive_buffers); - tasklet_enable (&instance->receive_tasklet); + tasklet_enable(&instance->receive_tasklet); for (i = 0; i < num_rcv_urbs; i++) - usb_free_urb (instance->receivers [i].urb); + usb_free_urb(instance->receivers[i].urb); for (i = 0; i < num_rcv_bufs; i++) - kfree (instance->receive_buffers [i].base); + kfree(instance->receive_buffers[i].base); /* send finalize */ - tasklet_disable (&instance->send_tasklet); + tasklet_disable(&instance->send_tasklet); for (i = 0; i < num_snd_urbs; i++) - if ((result = usb_unlink_urb (instance->senders [i].urb)) < 0) - dbg ("udsl_instance_disconnect: usb_unlink_urb on send urb %d returned %d!", i, result); - - /* wait for completion handlers to finish */ - do { - count = 0; - spin_lock_irq (&instance->send_lock); - list_for_each (pos, &instance->spare_senders) - DEBUG_ON (++count > num_snd_urbs); - spin_unlock_irq (&instance->send_lock); - - dbg ("udsl_instance_disconnect: found %u spare senders", count); - - if (count == num_snd_urbs) - break; - - set_current_state (TASK_RUNNING); - schedule (); - } while (1); + usb_kill_urb(instance->senders[i].urb); /* no need to take the spinlock */ - INIT_LIST_HEAD (&instance->spare_senders); - INIT_LIST_HEAD (&instance->spare_send_buffers); + INIT_LIST_HEAD(&instance->spare_senders); + INIT_LIST_HEAD(&instance->spare_send_buffers); instance->current_buffer = NULL; - tasklet_enable (&instance->send_tasklet); + tasklet_enable(&instance->send_tasklet); for (i = 0; i < num_snd_urbs; i++) - usb_free_urb (instance->senders [i].urb); + usb_free_urb(instance->senders[i].urb); for (i = 0; i < num_snd_bufs; i++) - kfree (instance->send_buffers [i].base); + kfree(instance->send_buffers[i].base); /* ATM finalize */ - shutdown_atm_dev (instance->atm_dev); + shutdown_atm_dev(instance->atm_dev); } EXPORT_SYMBOL_GPL(udsl_get_instance); @@ -1144,48 +1146,50 @@ EXPORT_SYMBOL_GPL(udsl_instance_disconnect); ** init ** ***********/ -static int __init udsl_usb_init (void) +static int __init udsl_usb_init(void) { - dbg ("udsl_usb_init: driver version " DRIVER_VERSION); + dbg("udsl_usb_init: driver version " DRIVER_VERSION); - if (sizeof (struct udsl_control) > sizeof (((struct sk_buff *)0)->cb)) { - printk (KERN_ERR __FILE__ ": unusable with this kernel!\n"); + if (sizeof(struct udsl_control) > sizeof(((struct sk_buff *) 0)->cb)) { + printk(KERN_ERR __FILE__ ": unusable with this kernel!\n"); return -EIO; } - if ((num_rcv_urbs > UDSL_MAX_RCV_URBS) || (num_snd_urbs > UDSL_MAX_SND_URBS) || - (num_rcv_bufs > UDSL_MAX_RCV_BUFS) || (num_snd_bufs > UDSL_MAX_SND_BUFS) || - (rcv_buf_size > UDSL_MAX_RCV_BUF_SIZE) || (snd_buf_size > UDSL_MAX_SND_BUF_SIZE)) + if ((num_rcv_urbs > UDSL_MAX_RCV_URBS) + || (num_snd_urbs > UDSL_MAX_SND_URBS) + || (num_rcv_bufs > UDSL_MAX_RCV_BUFS) + || (num_snd_bufs > UDSL_MAX_SND_BUFS) + || (rcv_buf_size > UDSL_MAX_RCV_BUF_SIZE) + || (snd_buf_size > UDSL_MAX_SND_BUF_SIZE)) return -EINVAL; return 0; } -module_init (udsl_usb_init); - -MODULE_AUTHOR (DRIVER_AUTHOR); -MODULE_DESCRIPTION (DRIVER_DESC); -MODULE_LICENSE ("GPL"); -MODULE_VERSION (DRIVER_VERSION); +module_init(udsl_usb_init); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRIVER_VERSION); /************ ** debug ** ************/ #ifdef VERBOSE_DEBUG -static int udsl_print_packet (const unsigned char *data, int len) +static int udsl_print_packet(const unsigned char *data, int len) { - unsigned char buffer [256]; + unsigned char buffer[256]; int i = 0, j = 0; for (i = 0; i < len;) { - buffer [0] = '\0'; - sprintf (buffer, "%.3d :", i); + buffer[0] = '\0'; + sprintf(buffer, "%.3d :", i); for (j = 0; (j < 16) && (i < len); j++, i++) { - sprintf (buffer, "%s %2.2x", buffer, data [i]); + sprintf(buffer, "%s %2.2x", buffer, data[i]); } - dbg ("%s", buffer); + dbg("%s", buffer); } return i; } diff --git a/drivers/usb/atm/usb_atm.h b/drivers/usb/atm/usb_atm.h index 1eef3571d312..188e9171073e 100644 --- a/drivers/usb/atm/usb_atm.h +++ b/drivers/usb/atm/usb_atm.h @@ -32,14 +32,14 @@ #define UDSL_MAX_SND_URBS 4 #define UDSL_MAX_RCV_BUFS 8 #define UDSL_MAX_SND_BUFS 8 -#define UDSL_MAX_RCV_BUF_SIZE 1024 /* ATM cells */ -#define UDSL_MAX_SND_BUF_SIZE 1024 /* ATM cells */ +#define UDSL_MAX_RCV_BUF_SIZE 1024 /* ATM cells */ +#define UDSL_MAX_SND_BUF_SIZE 1024 /* ATM cells */ #define UDSL_DEFAULT_RCV_URBS 2 #define UDSL_DEFAULT_SND_URBS 2 #define UDSL_DEFAULT_RCV_BUFS 4 #define UDSL_DEFAULT_SND_BUFS 4 -#define UDSL_DEFAULT_RCV_BUF_SIZE 64 /* ATM cells */ -#define UDSL_DEFAULT_SND_BUF_SIZE 64 /* ATM cells */ +#define UDSL_DEFAULT_RCV_BUF_SIZE 64 /* ATM cells */ +#define UDSL_DEFAULT_SND_BUF_SIZE 64 /* ATM cells */ #define ATM_CELL_HEADER (ATM_CELL_SIZE - ATM_CELL_PAYLOAD) #define UDSL_NUM_CELLS(x) (((x) + ATM_AAL5_TRAILER + ATM_CELL_PAYLOAD - 1) / ATM_CELL_PAYLOAD) @@ -91,8 +91,8 @@ struct udsl_control { unsigned int num_cells; unsigned int num_entire; unsigned int pdu_padding; - unsigned char cell_header [ATM_CELL_HEADER]; - unsigned char aal5_trailer [ATM_AAL5_TRAILER]; + unsigned char cell_header[ATM_CELL_HEADER]; + unsigned char aal5_trailer[ATM_AAL5_TRAILER]; }; #define UDSL_SKB(x) ((struct udsl_control *)(x)->cb) @@ -111,7 +111,7 @@ struct udsl_instance_data { /* USB device part */ struct usb_device *usb_dev; - char description [64]; + char description[64]; int data_endpoint; int snd_padding; int rcv_padding; @@ -127,8 +127,8 @@ struct udsl_instance_data { wait_queue_head_t firmware_waiters; /* receive */ - struct udsl_receiver receivers [UDSL_MAX_RCV_URBS]; - struct udsl_receive_buffer receive_buffers [UDSL_MAX_RCV_BUFS]; + struct udsl_receiver receivers[UDSL_MAX_RCV_URBS]; + struct udsl_receive_buffer receive_buffers[UDSL_MAX_RCV_BUFS]; spinlock_t receive_lock; struct list_head spare_receivers; @@ -138,8 +138,8 @@ struct udsl_instance_data { struct list_head spare_receive_buffers; /* send */ - struct udsl_sender senders [UDSL_MAX_SND_URBS]; - struct udsl_send_buffer send_buffers [UDSL_MAX_SND_BUFS]; + struct udsl_sender senders[UDSL_MAX_SND_URBS]; + struct udsl_send_buffer send_buffers[UDSL_MAX_SND_BUFS]; struct sk_buff_head sndqueue; @@ -153,7 +153,8 @@ struct udsl_instance_data { struct list_head filled_send_buffers; }; -extern int udsl_instance_setup(struct usb_device *dev, struct udsl_instance_data *instance); -extern void udsl_instance_disconnect (struct udsl_instance_data *instance); +extern int udsl_instance_setup(struct usb_device *dev, + struct udsl_instance_data *instance); +extern void udsl_instance_disconnect(struct udsl_instance_data *instance); extern void udsl_get_instance(struct udsl_instance_data *instance); extern void udsl_put_instance(struct udsl_instance_data *instance); -- cgit v1.2.3 From afb83cf9fd258cc10227f8790a7f3a07c1d263f9 Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Tue, 5 Oct 2004 23:27:59 +0100 Subject: NTFS: Add a helper function fs/ntfs/aops.c::mark_ntfs_record_dirty() which marks all buffers belonging to an ntfs record dirty, followed by marking the page the ntfs record is in dirty and also marking the vfs inode containing the ntfs record dirty (I_DIRTY_PAGES). Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 4 ++++ fs/ntfs/aops.c | 42 ++++++++++++++++++++++++++++++++++++++++++ fs/ntfs/aops.h | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 fs/ntfs/aops.h diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 2f89834fea9f..54e04f293543 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -47,6 +47,10 @@ ToDo/Notes: - Implement fs/ntfs/runlist.c::ntfs_rl_truncate_nolock(). - Add MFT_RECORD_OLD as a copy of MFT_RECORD in fs/ntfs/layout.h and change MFT_RECORD to contain the NTFS 3.1+ specific fields. + - Add a helper function fs/ntfs/aops.c::mark_ntfs_record_dirty() which + marks all buffers belonging to an ntfs record dirty, followed by + marking the page the ntfs record is in dirty and also marking the vfs + inode containing the ntfs record dirty (I_DIRTY_PAGES). 2.1.20 - Fix two stupid bugs introduced in 2.1.18 release. diff --git a/fs/ntfs/aops.c b/fs/ntfs/aops.c index f6e7922e7ab6..f09a3157c0c4 100644 --- a/fs/ntfs/aops.c +++ b/fs/ntfs/aops.c @@ -27,6 +27,7 @@ #include #include +#include "aops.h" #include "ntfs.h" /** @@ -2028,3 +2029,44 @@ struct address_space_operations ntfs_mst_aops = { belonging to the page. */ #endif /* NTFS_RW */ }; + +#ifdef NTFS_RW + +/** + * mark_ntfs_record_dirty - mark an ntfs record dirty + * @ni: ntfs inode to which the ntfs record to be marked dirty belongs + * @page: page containing the ntfs record to mark dirty + * @rec_start: byte offset within @page at which the ntfs record begins + * + * If the ntfs record is the same size as the page cache page @page, set all + * buffers in the page dirty. Otherwise, set only the buffers in which the + * ntfs record is located dirty. + * + * Also, set the page containing the ntfs record dirty, which also marks the + * vfs inode the ntfs record belongs to dirty (I_DIRTY_PAGES). + */ +void mark_ntfs_record_dirty(ntfs_inode *ni, struct page *page, + unsigned int rec_start) { + struct buffer_head *bh, *head; + unsigned int rec_end, bh_size, bh_start, bh_end; + + BUG_ON(!page); + BUG_ON(!page_has_buffers(page)); + if (ni->itype.index.block_size == PAGE_CACHE_SIZE) { + __set_page_dirty_buffers(page); + return; + } + rec_end = rec_start + ni->itype.index.block_size; + bh_size = ni->vol->sb->s_blocksize; + bh_start = 0; + bh = head = page_buffers(page); + do { + bh_end = bh_start + bh_size; + if ((bh_start >= rec_start) && (bh_end <= rec_end)) + set_buffer_dirty(bh); + bh_start = bh_end; + } while ((bh = bh->b_this_page) != head); + __set_page_dirty_nobuffers(page); +} + +#endif /* NTFS_RW */ diff --git a/fs/ntfs/aops.h b/fs/ntfs/aops.h new file mode 100644 index 000000000000..d1abd9e06467 --- /dev/null +++ b/fs/ntfs/aops.h @@ -0,0 +1,36 @@ +/** + * aops.h - Defines for NTFS kernel address space operations and page cache + * handling. Part of the Linux-NTFS project. + * + * Copyright (c) 2001-2004 Anton Altaparmakov + * Copyright (c) 2002 Richard Russon + * + * This program/include file is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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. + * + * You should have received a copy of the GNU General Public License + * along with this program (in the main directory of the Linux-NTFS + * distribution in the file COPYING); if not, write to the Free Software + * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _LINUX_NTFS_AOPS_H +#define _LINUX_NTFS_AOPS_H + +#ifdef NTFS_RW + +#include "inode.h" + +extern void mark_ntfs_record_dirty(ntfs_inode *ni, struct page *page, + unsigned int rec_start); + +#endif /* NTFS_RW */ + +#endif /* _LINUX_NTFS_AOPS_H */ -- cgit v1.2.3 From 29b9032b1a96d7ffb8ed6e6064cc6ad2072b9fa3 Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Tue, 5 Oct 2004 23:32:49 +0100 Subject: NTFS: Switch fs/ntfs/index.h::ntfs_index_entry_mark_dirty() to using the new helper fs/ntfs/aops.c::mark_ntfs_record_dirty() and remove the no longer needed fs/ntfs/index.[hc]::__ntfs_index_entry_mark_dirty(). Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 3 +++ fs/ntfs/index.c | 55 ------------------------------------------------------- fs/ntfs/index.h | 6 +++--- 3 files changed, 6 insertions(+), 58 deletions(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 54e04f293543..78208d9bc673 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -51,6 +51,9 @@ ToDo/Notes: marks all buffers belonging to an ntfs record dirty, followed by marking the page the ntfs record is in dirty and also marking the vfs inode containing the ntfs record dirty (I_DIRTY_PAGES). + - Switch fs/ntfs/index.h::ntfs_index_entry_mark_dirty() to using the + new helper fs/ntfs/aops.c::mark_ntfs_record_dirty() and remove the no + longer needed fs/ntfs/index.[hc]::__ntfs_index_entry_mark_dirty(). 2.1.20 - Fix two stupid bugs introduced in 2.1.18 release. diff --git a/fs/ntfs/index.c b/fs/ntfs/index.c index b19a4882b689..11be36f68bc9 100644 --- a/fs/ntfs/index.c +++ b/fs/ntfs/index.c @@ -459,58 +459,3 @@ idx_err_out: err = -EIO; goto err_out; } - -#ifdef NTFS_RW - -/** - * __ntfs_index_entry_mark_dirty - mark an index allocation entry dirty - * @ictx: ntfs index context describing the index entry - * - * NOTE: You want to use fs/ntfs/index.h::ntfs_index_entry_mark_dirty() instead! - * - * Mark the index allocation entry described by the index entry context @ictx - * dirty. - * - * The index entry must be in an index block belonging to the index allocation - * attribute. Mark the buffers belonging to the index record as well as the - * page cache page the index block is in dirty. This automatically marks the - * VFS inode of the ntfs index inode to which the index entry belongs dirty, - * too (I_DIRTY_PAGES) and this in turn ensures the page buffers, and hence the - * dirty index block, will be written out to disk later. - */ -void __ntfs_index_entry_mark_dirty(ntfs_index_context *ictx) -{ - ntfs_inode *ni; - struct page *page; - struct buffer_head *bh, *head; - unsigned int rec_start, rec_end, bh_size, bh_start, bh_end; - - BUG_ON(ictx->is_in_root); - ni = ictx->idx_ni; - page = ictx->page; - BUG_ON(!page_has_buffers(page)); - /* - * If the index block is the same size as the page cache page, set all - * the buffers in the page, as well as the page itself, dirty. - */ - if (ni->itype.index.block_size == PAGE_CACHE_SIZE) { - __set_page_dirty_buffers(page); - return; - } - /* Set only the buffers in which the index block is located dirty. */ - rec_start = (unsigned int)((u8*)ictx->ia - (u8*)page_address(page)); - rec_end = rec_start + ni->itype.index.block_size; - bh_size = ni->vol->sb->s_blocksize; - bh_start = 0; - bh = head = page_buffers(page); - do { - bh_end = bh_start + bh_size; - if ((bh_start >= rec_start) && (bh_end <= rec_end)) - set_buffer_dirty(bh); - bh_start = bh_end; - } while ((bh = bh->b_this_page) != head); - /* Finally, set the page itself dirty, too. */ - __set_page_dirty_nobuffers(page); -} - -#endif /* NTFS_RW */ diff --git a/fs/ntfs/index.h b/fs/ntfs/index.h index 159442dfa90a..b8f503fa2d6e 100644 --- a/fs/ntfs/index.h +++ b/fs/ntfs/index.h @@ -30,6 +30,7 @@ #include "inode.h" #include "attrib.h" #include "mft.h" +#include "aops.h" /** * @idx_ni: index inode containing the @entry described by this context @@ -115,8 +116,6 @@ static inline void ntfs_index_entry_flush_dcache_page(ntfs_index_context *ictx) flush_dcache_page(ictx->page); } -extern void __ntfs_index_entry_mark_dirty(ntfs_index_context *ictx); - /** * ntfs_index_entry_mark_dirty - mark an index entry dirty * @ictx: ntfs index context describing the index entry @@ -140,7 +139,8 @@ static inline void ntfs_index_entry_mark_dirty(ntfs_index_context *ictx) if (ictx->is_in_root) mark_mft_record_dirty(ictx->actx->ntfs_ino); else - __ntfs_index_entry_mark_dirty(ictx); + mark_ntfs_record_dirty(ictx->idx_ni, ictx->page, + (u8*)ictx->ia - (u8*)page_address(ictx->page)); } #endif /* NTFS_RW */ -- cgit v1.2.3 From 64a050305d12a5b694ded683e6973abf2aefcdb7 Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Wed, 6 Oct 2004 03:22:40 -0700 Subject: [PATCH] USB: Fix hiddev devfs oops From: Herbert Xu There is a long-standing devfs_unregister oops in hid/hiddev. It's caused by hid calling hiddev_exit before unregistering itself which in turn calls hiddev_disconnect. hiddev_exit removes the directory which contains the hiddev devices. Therefore it needs to be called after the hiddev devices have been disconnected. Signed-off-by: Herbert Xu Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- drivers/usb/input/hid-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/input/hid-core.c b/drivers/usb/input/hid-core.c index fbeb6b728e2e..5c9f01790aee 100644 --- a/drivers/usb/input/hid-core.c +++ b/drivers/usb/input/hid-core.c @@ -1867,8 +1867,8 @@ hiddev_init_fail: static void __exit hid_exit(void) { - hiddev_exit(); usb_deregister(&hid_driver); + hiddev_exit(); } module_init(hid_init); -- cgit v1.2.3 From eef30f58b4da4f0e0891af085e5f40f5f2155b38 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Wed, 6 Oct 2004 15:25:20 +0100 Subject: USB: Fix assertion logic in USB ATM core. We assert something we believe to be _true_, not false. Signed-off-by: David Woodhouse Signed-off-by: Greg Kroah-Hartman --- drivers/usb/atm/usb_atm.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/atm/usb_atm.c b/drivers/usb/atm/usb_atm.c index 486458d86fe1..ac9c80600a6e 100644 --- a/drivers/usb/atm/usb_atm.c +++ b/drivers/usb/atm/usb_atm.c @@ -95,9 +95,9 @@ #include #ifdef DEBUG -#define UDSL_ASSERT(x) BUG_ON(x) +#define UDSL_ASSERT(x) BUG_ON(!(x)) #else -#define UDSL_ASSERT(x) +#define UDSL_ASSERT(x) warn("failed assertion '" #x "' at line %d", __LINE__) #endif #ifdef VERBOSE_DEBUG @@ -406,7 +406,7 @@ static unsigned int udsl_write_cells(struct udsl_instance_data *instance, target += ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER; --ctrl->num_cells; - UDSL_ASSERT(ctrl->num_cells); + UDSL_ASSERT(!ctrl->num_cells); } memcpy(target, ctrl->aal5_trailer, ATM_AAL5_TRAILER); @@ -445,7 +445,7 @@ static void udsl_complete_receive(struct urb *urb, struct pt_regs *regs) vdbg("udsl_complete_receive: urb 0x%p, status %d, actual_length %d, filled_cells %u, rcv 0x%p, buf 0x%p", urb, urb->status, urb->actual_length, buf->filled_cells, rcv, buf); - UDSL_ASSERT(buf->filled_cells > rcv_buf_size); + UDSL_ASSERT(buf->filled_cells <= rcv_buf_size); /* may not be in_interrupt() */ spin_lock_irqsave(&instance->receive_lock, flags); -- cgit v1.2.3 From 0c6ad673d45d6c4e49a226ffea05d86cbecdcea1 Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Thu, 7 Oct 2004 13:10:19 +0100 Subject: NTFS: - Move ntfs_{un,}map_page() from ntfs.h to aops.h and fix resulting include errors. - Move typedefs for runlist_element and runlist from ntfs.h to runlist.h and fix resulting include errors. Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 4 +++ fs/ntfs/aops.c | 4 +++ fs/ntfs/aops.h | 69 +++++++++++++++++++++++++++++++++++++++++++++++- fs/ntfs/attrib.c | 4 +++ fs/ntfs/bitmap.c | 1 + fs/ntfs/collate.c | 3 ++- fs/ntfs/compress.c | 2 ++ fs/ntfs/dir.c | 8 +++++- fs/ntfs/dir.h | 2 ++ fs/ntfs/file.c | 4 +++ fs/ntfs/index.c | 4 ++- fs/ntfs/inode.c | 3 +++ fs/ntfs/inode.h | 7 +++++ fs/ntfs/lcnalloc.c | 1 + fs/ntfs/lcnalloc.h | 1 + fs/ntfs/logfile.c | 4 ++- fs/ntfs/malloc.h | 1 + fs/ntfs/mft.c | 8 +++++- fs/ntfs/mft.h | 2 ++ fs/ntfs/namei.c | 4 ++- fs/ntfs/ntfs.h | 77 ++---------------------------------------------------- fs/ntfs/quota.c | 3 ++- fs/ntfs/runlist.c | 4 ++- fs/ntfs/runlist.h | 28 ++++++++++++++++++++ fs/ntfs/super.c | 5 +++- fs/ntfs/types.h | 28 -------------------- fs/ntfs/unistr.c | 2 ++ fs/ntfs/upcase.c | 1 + fs/ntfs/volume.h | 2 ++ 29 files changed, 173 insertions(+), 113 deletions(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 78208d9bc673..7b311ebd5318 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -54,6 +54,10 @@ ToDo/Notes: - Switch fs/ntfs/index.h::ntfs_index_entry_mark_dirty() to using the new helper fs/ntfs/aops.c::mark_ntfs_record_dirty() and remove the no longer needed fs/ntfs/index.[hc]::__ntfs_index_entry_mark_dirty(). + - Move ntfs_{un,}map_page() from ntfs.h to aops.h and fix resulting + include errors. + - Move the typedefs for runlist_element and runlist from types.h to + runlist.h and fix resulting include errors. 2.1.20 - Fix two stupid bugs introduced in 2.1.18 release. diff --git a/fs/ntfs/aops.c b/fs/ntfs/aops.c index f09a3157c0c4..61da8d51fd6e 100644 --- a/fs/ntfs/aops.c +++ b/fs/ntfs/aops.c @@ -28,6 +28,10 @@ #include #include "aops.h" +#include "debug.h" +#include "inode.h" +#include "mft.h" +#include "types.h" #include "ntfs.h" /** diff --git a/fs/ntfs/aops.h b/fs/ntfs/aops.h index d1abd9e06467..2bdadc1d533b 100644 --- a/fs/ntfs/aops.h +++ b/fs/ntfs/aops.h @@ -24,10 +24,77 @@ #ifndef _LINUX_NTFS_AOPS_H #define _LINUX_NTFS_AOPS_H -#ifdef NTFS_RW +#include +#include +#include +#include #include "inode.h" +/** + * ntfs_unmap_page - release a page that was mapped using ntfs_map_page() + * @page: the page to release + * + * Unpin, unmap and release a page that was obtained from ntfs_map_page(). + */ +static inline void ntfs_unmap_page(struct page *page) +{ + kunmap(page); + page_cache_release(page); +} + +/** + * ntfs_map_page - map a page into accessible memory, reading it if necessary + * @mapping: address space for which to obtain the page + * @index: index into the page cache for @mapping of the page to map + * + * Read a page from the page cache of the address space @mapping at position + * @index, where @index is in units of PAGE_CACHE_SIZE, and not in bytes. + * + * If the page is not in memory it is loaded from disk first using the readpage + * method defined in the address space operations of @mapping and the page is + * added to the page cache of @mapping in the process. + * + * If the page is in high memory it is mapped into memory directly addressible + * by the kernel. + * + * Finally the page count is incremented, thus pinning the page into place. + * + * The above means that page_address(page) can be used on all pages obtained + * with ntfs_map_page() to get the kernel virtual address of the page. + * + * When finished with the page, the caller has to call ntfs_unmap_page() to + * unpin, unmap and release the page. + * + * Note this does not grant exclusive access. If such is desired, the caller + * must provide it independently of the ntfs_{un}map_page() calls by using + * a {rw_}semaphore or other means of serialization. A spin lock cannot be + * used as ntfs_map_page() can block. + * + * The unlocked and uptodate page is returned on success or an encoded error + * on failure. Caller has to test for error using the IS_ERR() macro on the + * return value. If that evaluates to TRUE, the negative error code can be + * obtained using PTR_ERR() on the return value of ntfs_map_page(). + */ +static inline struct page *ntfs_map_page(struct address_space *mapping, + unsigned long index) +{ + struct page *page = read_cache_page(mapping, index, + (filler_t*)mapping->a_ops->readpage, NULL); + + if (!IS_ERR(page)) { + wait_on_page_locked(page); + kmap(page); + if (PageUptodate(page) && !PageError(page)) + return page; + ntfs_unmap_page(page); + return ERR_PTR(-EIO); + } + return page; +} + +#ifdef NTFS_RW + extern void mark_ntfs_record_dirty(ntfs_inode *ni, struct page *page, unsigned int rec_start); diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index b8db01aaf393..865de0c2c3d0 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -21,6 +21,10 @@ */ #include + +#include "attrib.h" +#include "debug.h" +#include "mft.h" #include "ntfs.h" /** diff --git a/fs/ntfs/bitmap.c b/fs/ntfs/bitmap.c index b8f06111f6ef..12cf2e30c7dd 100644 --- a/fs/ntfs/bitmap.c +++ b/fs/ntfs/bitmap.c @@ -25,6 +25,7 @@ #include "bitmap.h" #include "debug.h" +#include "aops.h" #include "ntfs.h" /** diff --git a/fs/ntfs/collate.c b/fs/ntfs/collate.c index 31dd894a4319..4a28ab3898ef 100644 --- a/fs/ntfs/collate.c +++ b/fs/ntfs/collate.c @@ -19,8 +19,9 @@ * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "ntfs.h" #include "collate.h" +#include "debug.h" +#include "ntfs.h" static int ntfs_collate_binary(ntfs_volume *vol, const void *data1, const int data1_len, diff --git a/fs/ntfs/compress.c b/fs/ntfs/compress.c index b4554e4bbbec..b96fe5ff68dd 100644 --- a/fs/ntfs/compress.c +++ b/fs/ntfs/compress.c @@ -25,6 +25,8 @@ #include #include +#include "inode.h" +#include "debug.h" #include "ntfs.h" /** diff --git a/fs/ntfs/dir.c b/fs/ntfs/dir.c index ed9838e0c5d0..2700e4bd5574 100644 --- a/fs/ntfs/dir.c +++ b/fs/ntfs/dir.c @@ -21,8 +21,14 @@ */ #include -#include "ntfs.h" +#include + #include "dir.h" +#include "aops.h" +#include "attrib.h" +#include "mft.h" +#include "debug.h" +#include "ntfs.h" /** * The little endian Unicode string $I30 as a global constant. diff --git a/fs/ntfs/dir.h b/fs/ntfs/dir.h index 90a8c3f65203..aea7582d561f 100644 --- a/fs/ntfs/dir.h +++ b/fs/ntfs/dir.h @@ -24,6 +24,8 @@ #define _LINUX_NTFS_DIR_H #include "layout.h" +#include "inode.h" +#include "types.h" /* * ntfs_name is used to return the file name to the caller of diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c index c7880d55b250..e808649d5a8f 100644 --- a/fs/ntfs/file.c +++ b/fs/ntfs/file.c @@ -19,6 +19,10 @@ * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include +#include + +#include "debug.h" #include "ntfs.h" /** diff --git a/fs/ntfs/index.c b/fs/ntfs/index.c index 11be36f68bc9..aded65d13a65 100644 --- a/fs/ntfs/index.c +++ b/fs/ntfs/index.c @@ -19,9 +19,11 @@ * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "ntfs.h" +#include "aops.h" #include "collate.h" +#include "debug.h" #include "index.h" +#include "ntfs.h" /** * ntfs_index_ctx_get - allocate and initialize a new index context diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 8c48de078c41..a592f57b5dbb 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -27,8 +27,11 @@ #include "ntfs.h" #include "dir.h" +#include "debug.h" #include "inode.h" #include "attrib.h" +#include "malloc.h" +#include "mft.h" #include "time.h" /** diff --git a/fs/ntfs/inode.h b/fs/ntfs/inode.h index 3aa7b873fe0d..bf1d906d1691 100644 --- a/fs/ntfs/inode.h +++ b/fs/ntfs/inode.h @@ -24,10 +24,17 @@ #ifndef _LINUX_NTFS_INODE_H #define _LINUX_NTFS_INODE_H +#include +#include #include +#include +#include +#include #include "layout.h" #include "volume.h" +#include "types.h" +#include "runlist.h" typedef struct _ntfs_inode ntfs_inode; diff --git a/fs/ntfs/lcnalloc.c b/fs/ntfs/lcnalloc.c index 5f291021a193..17888c9f02f1 100644 --- a/fs/ntfs/lcnalloc.c +++ b/fs/ntfs/lcnalloc.c @@ -30,6 +30,7 @@ #include "volume.h" #include "attrib.h" #include "malloc.h" +#include "aops.h" #include "ntfs.h" /** diff --git a/fs/ntfs/lcnalloc.h b/fs/ntfs/lcnalloc.h index b9ebb31100a1..4cac1c024af6 100644 --- a/fs/ntfs/lcnalloc.h +++ b/fs/ntfs/lcnalloc.h @@ -28,6 +28,7 @@ #include #include "types.h" +#include "runlist.h" #include "volume.h" typedef enum { diff --git a/fs/ntfs/logfile.c b/fs/ntfs/logfile.c index 84ed2ef14a32..9f20835e04a9 100644 --- a/fs/ntfs/logfile.c +++ b/fs/ntfs/logfile.c @@ -29,8 +29,10 @@ #include "logfile.h" #include "volume.h" -#include "ntfs.h" +#include "aops.h" #include "debug.h" +#include "malloc.h" +#include "ntfs.h" /** * ntfs_check_restart_page_header - check the page header for consistency diff --git a/fs/ntfs/malloc.h b/fs/ntfs/malloc.h index c8548a5336e0..fac5944df6d8 100644 --- a/fs/ntfs/malloc.h +++ b/fs/ntfs/malloc.h @@ -24,6 +24,7 @@ #include #include +#include /** * ntfs_malloc_nofs - allocate memory in multiples of pages diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 4b9be65ca33d..99aa4decb9c2 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -20,10 +20,16 @@ * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include #include -#include "ntfs.h" #include "bitmap.h" +#include "lcnalloc.h" +#include "aops.h" +#include "debug.h" +#include "mft.h" +#include "malloc.h" +#include "ntfs.h" /** * __format_mft_record - initialize an empty mft record diff --git a/fs/ntfs/mft.h b/fs/ntfs/mft.h index 5f3566779fa8..924f87abff8b 100644 --- a/fs/ntfs/mft.h +++ b/fs/ntfs/mft.h @@ -24,6 +24,8 @@ #define _LINUX_NTFS_MFT_H #include +#include +#include #include "inode.h" diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c index 70b503a85f4d..b47084f0607b 100644 --- a/fs/ntfs/namei.c +++ b/fs/ntfs/namei.c @@ -23,8 +23,10 @@ #include #include -#include "ntfs.h" #include "dir.h" +#include "debug.h" +#include "mft.h" +#include "ntfs.h" /** * ntfs_lookup - find the inode represented by a dentry in a directory inode diff --git a/fs/ntfs/ntfs.h b/fs/ntfs/ntfs.h index c3fda17158e6..d65c401ccbc3 100644 --- a/fs/ntfs/ntfs.h +++ b/fs/ntfs/ntfs.h @@ -29,21 +29,12 @@ #include #include #include -#include #include -#include #include -#include #include "types.h" -#include "debug.h" -#include "malloc.h" -#include "endian.h" #include "volume.h" -#include "inode.h" #include "layout.h" -#include "attrib.h" -#include "mft.h" typedef enum { NTFS_BLOCK_SIZE = 512, @@ -87,72 +78,12 @@ static inline ntfs_volume *NTFS_SB(struct super_block *sb) return sb->s_fs_info; } -/** - * ntfs_unmap_page - release a page that was mapped using ntfs_map_page() - * @page: the page to release - * - * Unpin, unmap and release a page that was obtained from ntfs_map_page(). - */ -static inline void ntfs_unmap_page(struct page *page) -{ - kunmap(page); - page_cache_release(page); -} - -/** - * ntfs_map_page - map a page into accessible memory, reading it if necessary - * @mapping: address space for which to obtain the page - * @index: index into the page cache for @mapping of the page to map - * - * Read a page from the page cache of the address space @mapping at position - * @index, where @index is in units of PAGE_CACHE_SIZE, and not in bytes. - * - * If the page is not in memory it is loaded from disk first using the readpage - * method defined in the address space operations of @mapping and the page is - * added to the page cache of @mapping in the process. - * - * If the page is in high memory it is mapped into memory directly addressible - * by the kernel. - * - * Finally the page count is incremented, thus pinning the page into place. - * - * The above means that page_address(page) can be used on all pages obtained - * with ntfs_map_page() to get the kernel virtual address of the page. - * - * When finished with the page, the caller has to call ntfs_unmap_page() to - * unpin, unmap and release the page. - * - * Note this does not grant exclusive access. If such is desired, the caller - * must provide it independently of the ntfs_{un}map_page() calls by using - * a {rw_}semaphore or other means of serialization. A spin lock cannot be - * used as ntfs_map_page() can block. - * - * The unlocked and uptodate page is returned on success or an encoded error - * on failure. Caller has to test for error using the IS_ERR() macro on the - * return value. If that evaluates to TRUE, the negative error code can be - * obtained using PTR_ERR() on the return value of ntfs_map_page(). - */ -static inline struct page *ntfs_map_page(struct address_space *mapping, - unsigned long index) -{ - struct page *page = read_cache_page(mapping, index, - (filler_t*)mapping->a_ops->readpage, NULL); - - if (!IS_ERR(page)) { - wait_on_page_locked(page); - kmap(page); - if (PageUptodate(page) && !PageError(page)) - return page; - ntfs_unmap_page(page); - return ERR_PTR(-EIO); - } - return page; -} - /* Declarations of functions and global variables. */ /* From fs/ntfs/compress.c */ extern int ntfs_read_compressed_block(struct page *page); +extern int allocate_compression_buffers(void); +extern void free_compression_buffers(void); /* From fs/ntfs/super.c */ #define default_upcase_len 0x10000 @@ -166,10 +97,6 @@ typedef struct { } option_t; extern const option_t on_errors_arr[]; -/* From fs/ntfs/compress.c */ -extern int allocate_compression_buffers(void); -extern void free_compression_buffers(void); - /* From fs/ntfs/mst.c */ extern int post_read_mst_fixup(NTFS_RECORD *b, const u32 size); extern int pre_write_mst_fixup(NTFS_RECORD *b, const u32 size); diff --git a/fs/ntfs/quota.c b/fs/ntfs/quota.c index b72a85dd446f..8764ebd8d063 100644 --- a/fs/ntfs/quota.c +++ b/fs/ntfs/quota.c @@ -22,9 +22,10 @@ #ifdef NTFS_RW -#include "ntfs.h" #include "index.h" #include "quota.h" +#include "debug.h" +#include "ntfs.h" /** * ntfs_mark_quotas_out_of_date - mark the quotas out of date on an ntfs volume diff --git a/fs/ntfs/runlist.c b/fs/ntfs/runlist.c index b900f2fde18d..6b9ca6883581 100644 --- a/fs/ntfs/runlist.c +++ b/fs/ntfs/runlist.c @@ -20,8 +20,10 @@ * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "ntfs.h" #include "dir.h" +#include "debug.h" +#include "malloc.h" +#include "ntfs.h" /** * ntfs_rl_mm - runlist memmove diff --git a/fs/ntfs/runlist.h b/fs/ntfs/runlist.h index 7d82f4bd87d3..7107fde59df9 100644 --- a/fs/ntfs/runlist.h +++ b/fs/ntfs/runlist.h @@ -28,6 +28,34 @@ #include "layout.h" #include "volume.h" +/** + * runlist_element - in memory vcn to lcn mapping array element + * @vcn: starting vcn of the current array element + * @lcn: starting lcn of the current array element + * @length: length in clusters of the current array element + * + * The last vcn (in fact the last vcn + 1) is reached when length == 0. + * + * When lcn == -1 this means that the count vcns starting at vcn are not + * physically allocated (i.e. this is a hole / data is sparse). + */ +typedef struct { /* In memory vcn to lcn mapping structure element. */ + VCN vcn; /* vcn = Starting virtual cluster number. */ + LCN lcn; /* lcn = Starting logical cluster number. */ + s64 length; /* Run length in clusters. */ +} runlist_element; + +/** + * runlist - in memory vcn to lcn mapping array including a read/write lock + * @rl: pointer to an array of runlist elements + * @lock: read/write spinlock for serializing access to @rl + * + */ +typedef struct { + runlist_element *rl; + struct rw_semaphore lock; +} runlist; + static inline void ntfs_init_runlist(runlist *rl) { rl->rl = NULL; diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c index 7d2a7b668f81..7edbdefd9d7a 100644 --- a/fs/ntfs/super.c +++ b/fs/ntfs/super.c @@ -31,12 +31,15 @@ #include #include -#include "ntfs.h" #include "sysctl.h" #include "logfile.h" #include "quota.h" #include "dir.h" +#include "debug.h" #include "index.h" +#include "aops.h" +#include "malloc.h" +#include "ntfs.h" /* Number of mounted file systems which have compression enabled. */ static unsigned long ntfs_nr_compression_users; diff --git a/fs/ntfs/types.h b/fs/ntfs/types.h index a98731ece42e..08a55aa53d4e 100644 --- a/fs/ntfs/types.h +++ b/fs/ntfs/types.h @@ -53,34 +53,6 @@ typedef sle64 leLCN; typedef s64 LSN; typedef sle64 leLSN; -/** - * runlist_element - in memory vcn to lcn mapping array element - * @vcn: starting vcn of the current array element - * @lcn: starting lcn of the current array element - * @length: length in clusters of the current array element - * - * The last vcn (in fact the last vcn + 1) is reached when length == 0. - * - * When lcn == -1 this means that the count vcns starting at vcn are not - * physically allocated (i.e. this is a hole / data is sparse). - */ -typedef struct { /* In memory vcn to lcn mapping structure element. */ - VCN vcn; /* vcn = Starting virtual cluster number. */ - LCN lcn; /* lcn = Starting logical cluster number. */ - s64 length; /* Run length in clusters. */ -} runlist_element; - -/** - * runlist - in memory vcn to lcn mapping array including a read/write lock - * @rl: pointer to an array of runlist elements - * @lock: read/write spinlock for serializing access to @rl - * - */ -typedef struct { - runlist_element *rl; - struct rw_semaphore lock; -} runlist; - typedef enum { FALSE = 0, TRUE = 1 diff --git a/fs/ntfs/unistr.c b/fs/ntfs/unistr.c index ec7405a80b4c..560b0ea255b0 100644 --- a/fs/ntfs/unistr.c +++ b/fs/ntfs/unistr.c @@ -19,6 +19,8 @@ * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "types.h" +#include "debug.h" #include "ntfs.h" /* diff --git a/fs/ntfs/upcase.c b/fs/ntfs/upcase.c index 276ed97982d3..879cdf1d5bd3 100644 --- a/fs/ntfs/upcase.c +++ b/fs/ntfs/upcase.c @@ -24,6 +24,7 @@ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "malloc.h" #include "ntfs.h" ntfschar *generate_default_upcase(void) diff --git a/fs/ntfs/volume.h b/fs/ntfs/volume.h index 4d0541350fff..4b97fa8635a8 100644 --- a/fs/ntfs/volume.h +++ b/fs/ntfs/volume.h @@ -24,6 +24,8 @@ #ifndef _LINUX_NTFS_VOLUME_H #define _LINUX_NTFS_VOLUME_H +#include + #include "types.h" #include "layout.h" -- cgit v1.2.3 From e91bcb8bb505f52e068929a9983ac3dbe137c6e3 Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Thu, 7 Oct 2004 13:15:17 +0100 Subject: NTFS: Remove unused {__,}format_mft_record() from fs/ntfs/mft.c. Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 1 + fs/ntfs/mft.c | 73 ------------------------------------------------------- 2 files changed, 1 insertion(+), 73 deletions(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 7b311ebd5318..18a423149b0f 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -58,6 +58,7 @@ ToDo/Notes: include errors. - Move the typedefs for runlist_element and runlist from types.h to runlist.h and fix resulting include errors. + - Remove unused {__,}format_mft_record() from fs/ntfs/mft.c. 2.1.20 - Fix two stupid bugs introduced in 2.1.18 release. diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 99aa4decb9c2..1bacb34022f6 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -31,79 +31,6 @@ #include "malloc.h" #include "ntfs.h" -/** - * __format_mft_record - initialize an empty mft record - * @m: mapped, pinned and locked for writing mft record - * @size: size of the mft record - * @rec_no: mft record number / inode number - * - * Private function to initialize an empty mft record. Use one of the two - * provided format_mft_record() functions instead. - */ -static void __format_mft_record(MFT_RECORD *m, const int size, - const unsigned long rec_no) -{ - ATTR_RECORD *a; - - memset(m, 0, size); - m->magic = magic_FILE; - /* Aligned to 2-byte boundary. */ - m->usa_ofs = cpu_to_le16((sizeof(MFT_RECORD) + 1) & ~1); - m->usa_count = cpu_to_le16(size / NTFS_BLOCK_SIZE + 1); - /* Set the update sequence number to 1. */ - *(le16*)((char*)m + ((sizeof(MFT_RECORD) + 1) & ~1)) = cpu_to_le16(1); - m->lsn = cpu_to_le64(0LL); - m->sequence_number = cpu_to_le16(1); - m->link_count = 0; - /* Aligned to 8-byte boundary. */ - m->attrs_offset = cpu_to_le16((le16_to_cpu(m->usa_ofs) + - (le16_to_cpu(m->usa_count) << 1) + 7) & ~7); - m->flags = 0; - /* - * Using attrs_offset plus eight bytes (for the termination attribute), - * aligned to 8-byte boundary. - */ - m->bytes_in_use = cpu_to_le32((le16_to_cpu(m->attrs_offset) + 8 + 7) & - ~7); - m->bytes_allocated = cpu_to_le32(size); - m->base_mft_record = cpu_to_le64((MFT_REF)0); - m->next_attr_instance = 0; - a = (ATTR_RECORD*)((char*)m + le16_to_cpu(m->attrs_offset)); - a->type = AT_END; - a->length = 0; -} - -/** - * format_mft_record - initialize an empty mft record - * @ni: ntfs inode of mft record - * @mft_rec: mapped, pinned and locked mft record (optional) - * - * Initialize an empty mft record. This is used when extending the MFT. - * - * If @mft_rec is NULL, we call map_mft_record() to obtain the - * record and we unmap it again when finished. - * - * We return 0 on success or -errno on error. - */ -int format_mft_record(ntfs_inode *ni, MFT_RECORD *mft_rec) -{ - MFT_RECORD *m; - - if (mft_rec) - m = mft_rec; - else { - m = map_mft_record(ni); - if (IS_ERR(m)) - return PTR_ERR(m); - } - __format_mft_record(m, ni->vol->mft_record_size, ni->mft_no); - if (!mft_rec) { - // FIXME: Need to set the mft record dirty! - unmap_mft_record(ni); - } - return 0; -} - /** * ntfs_readpage - external declaration, function is in fs/ntfs/aops.c */ -- cgit v1.2.3 From 6ca3ba608bfd0f1df6dabd7f3e976ab4f5dc531b Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Thu, 7 Oct 2004 13:28:53 +0100 Subject: NTFS: - Modify fs/ntfs/mft.c::__mark_mft_record_dirty() to use the helper mark_ntfs_record_dirty() which also changes the behaviour in that we now set the buffers belonging to the mft record dirty as well as the page itself. - Update fs/ntfs/mft.c::write_mft_record_nolock() and sync_mft_mirror() to cope with the fact that there now are dirty buffers in mft pages. Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 6 ++++++ fs/ntfs/mft.c | 40 +++++----------------------------------- 2 files changed, 11 insertions(+), 35 deletions(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 18a423149b0f..3722d0802626 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -59,6 +59,12 @@ ToDo/Notes: - Move the typedefs for runlist_element and runlist from types.h to runlist.h and fix resulting include errors. - Remove unused {__,}format_mft_record() from fs/ntfs/mft.c. + - Modify fs/ntfs/mft.c::__mark_mft_record_dirty() to use the helper + mark_ntfs_record_dirty() which also changes the behaviour in that we + now set the buffers belonging to the mft record dirty as well as the + page itself. + - Update fs/ntfs/mft.c::write_mft_record_nolock() and sync_mft_mirror() + to cope with the fact that there now are dirty buffers in mft pages. 2.1.20 - Fix two stupid bugs introduced in 2.1.18 release. diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 1bacb34022f6..c74ae7d72c3f 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -407,19 +407,11 @@ unm_err_out: */ void __mark_mft_record_dirty(ntfs_inode *ni) { - struct page *page = ni->page; ntfs_inode *base_ni; ntfs_debug("Entering for inode 0x%lx.", ni->mft_no); - BUG_ON(!page); BUG_ON(NInoAttr(ni)); - - /* - * Set the page containing the mft record dirty. This also marks the - * $MFT inode dirty (I_DIRTY_PAGES). - */ - __set_page_dirty_nobuffers(page); - + mark_ntfs_record_dirty(ni, ni->page, ni->page_ofs); /* Determine the base vfs inode and mark it dirty, too. */ down(&ni->extent_lock); if (likely(ni->nr_extents >= 0)) @@ -541,20 +533,9 @@ no_buffers_err_out: m_end = m_start + vol->mft_record_size; do { block_end = block_start + blocksize; - /* - * If the buffer is outside the mft record, just skip it, - * clearing it if it is dirty to make sure it is not written - * out. It should never be marked dirty but better be safe. - */ - if ((block_end <= m_start) || (block_start >= m_end)) { - if (buffer_dirty(bh)) { - ntfs_warning(vol->sb, "Clearing dirty mft " - "record page buffer. %s", - ntfs_please_email); - clear_buffer_dirty(bh); - } + /* If the buffer is outside the mft record, skip it. */ + if ((block_end <= m_start) || (block_start >= m_end)) continue; - } if (!buffer_mapped(bh)) { ntfs_error(vol->sb, "Writing mft mirror records " "without existing mapped buffers is " @@ -706,20 +687,9 @@ no_buffers_err_out: m_end = m_start + vol->mft_record_size; do { block_end = block_start + blocksize; - /* - * If the buffer is outside the mft record, just skip it, - * clearing it if it is dirty to make sure it is not written - * out. It should never be marked dirty but better be safe. - */ - if ((block_end <= m_start) || (block_start >= m_end)) { - if (buffer_dirty(bh)) { - ntfs_warning(vol->sb, "Clearing dirty mft " - "record page buffer. %s", - ntfs_please_email); - clear_buffer_dirty(bh); - } + /* If the buffer is outside the mft record, skip it. */ + if ((block_end <= m_start) || (block_start >= m_end)) continue; - } if (!buffer_mapped(bh)) { ntfs_error(vol->sb, "Writing mft records without " "existing mapped buffers is not " -- cgit v1.2.3 From b537028034c092322eb4c5d3e767d91478b3b053 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Thu, 7 Oct 2004 16:56:25 +0100 Subject: USB: SpeedTouch / ATM update: - Continue polling even if we receive a 'line down' interrupt. The Rev 4 modems give that one but not 'line up'. - Make the warn() on debugging assertions actually conditional :) - Keep a reference count on the module while its thread is running. - Provide empty exit function for usb_atm.ko so it's unloadable. - Handle firmware which is in /usr so only turns up later. Signed-off-by: David Woodhouse Signed-off-by: Greg Kroah-Hartman --- drivers/usb/atm/speedtch.c | 16 ++++++++++++---- drivers/usb/atm/usb_atm.c | 7 ++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/drivers/usb/atm/speedtch.c b/drivers/usb/atm/speedtch.c index 696541b57c88..13d0a50cadfc 100644 --- a/drivers/usb/atm/speedtch.c +++ b/drivers/usb/atm/speedtch.c @@ -316,7 +316,6 @@ static void speedtch_handle_int(struct urb *urb, struct pt_regs *regs) del_timer(&instance->poll_timer); printk(KERN_NOTICE "DSL line goes up\n"); } else if (!memcmp(down_int, instance->int_data, 6)) { - del_timer(&instance->poll_timer); printk(KERN_NOTICE "DSL line goes down\n"); } else { int i; @@ -464,7 +463,6 @@ static void speedtch_timer_poll(unsigned long data) } #ifdef USE_FW_LOADER - static void speedtch_upload_firmware(struct speedtch_instance_data *instance, const struct firmware *fw1, const struct firmware *fw2) @@ -639,6 +637,13 @@ static int speedtch_load_firmware(void *arg) release_firmware(fw1); } + /* In case we failed, set state back to NO_FIRMWARE so that + another later attempt may work. Otherwise, we never actually + manage to recover if, for example, the firmware is on /usr and + we look for it too early. */ + speedtch_got_firmware(instance, 0); + + module_put(THIS_MODULE); udsl_put_instance(&instance->u); return 0; } @@ -662,9 +667,10 @@ static void speedtch_firmware_start(struct speedtch_instance_data *instance) instance->u.status = UDSL_LOADING_FIRMWARE; up(&instance->u.serialize); +#ifdef USE_FW_LOADER udsl_get_instance(&instance->u); + try_module_get(THIS_MODULE); -#ifdef USE_FW_LOADER ret = kernel_thread(speedtch_load_firmware, instance, CLONE_FS | CLONE_FILES); @@ -673,10 +679,12 @@ static void speedtch_firmware_start(struct speedtch_instance_data *instance) dbg("speedtch_firmware_start: kernel_thread failed (%d)!", ret); + module_put(THIS_MODULE); + udsl_put_instance(&instance->u); /* Just pretend it never happened... hope modem_run happens */ #endif /* USE_FW_LOADER */ + speedtch_got_firmware(instance, 0); - udsl_put_instance(&instance->u); } static int speedtch_firmware_wait(struct udsl_instance_data *instance) diff --git a/drivers/usb/atm/usb_atm.c b/drivers/usb/atm/usb_atm.c index ac9c80600a6e..9180dda04fc5 100644 --- a/drivers/usb/atm/usb_atm.c +++ b/drivers/usb/atm/usb_atm.c @@ -97,7 +97,7 @@ #ifdef DEBUG #define UDSL_ASSERT(x) BUG_ON(!(x)) #else -#define UDSL_ASSERT(x) warn("failed assertion '" #x "' at line %d", __LINE__) +#define UDSL_ASSERT(x) do { if (!(x)) warn("failed assertion '" #x "' at line %d", __LINE__); } while(0) #endif #ifdef VERBOSE_DEBUG @@ -1166,7 +1166,12 @@ static int __init udsl_usb_init(void) return 0; } +static void __exit udsl_usb_exit(void) +{ +} + module_init(udsl_usb_init); +module_exit(udsl_usb_exit); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); -- cgit v1.2.3 From faed8cc6579ca73e64af753f6d110340acbf499c Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Sun, 10 Oct 2004 23:13:46 +0100 Subject: NTFS: Update fs/ntfs/inode.c::ntfs_write_inode() to also use the helper mark_ntfs_record_dirty() and thus to set the buffers belonging to the mft record dirty as well as the page itself. Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 3 +++ fs/ntfs/inode.c | 11 +++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 3722d0802626..f532f388ea3d 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -65,6 +65,9 @@ ToDo/Notes: page itself. - Update fs/ntfs/mft.c::write_mft_record_nolock() and sync_mft_mirror() to cope with the fact that there now are dirty buffers in mft pages. + - Update fs/ntfs/inode.c::ntfs_write_inode() to also use the helper + mark_ntfs_record_dirty() and thus to set the buffers belonging to the + mft record dirty as well as the page itself. 2.1.20 - Fix two stupid bugs introduced in 2.1.18 release. diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index a592f57b5dbb..e8354aa41971 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -2271,7 +2271,7 @@ int ntfs_show_options(struct seq_file *sf, struct vfsmount *mnt) * ntfs_truncate - called when the i_size of an ntfs inode is changed * @vi: inode for which the i_size was changed * - * We don't support i_size changes yet. + * We do not support i_size changes yet. * * The kernel guarantees that @vi is a regular file (S_ISREG() is true) and * that the change is allowed. @@ -2505,9 +2505,16 @@ int ntfs_write_inode(struct inode *vi, int sync) * dirty, since we are going to write this mft record below in any case * and the base mft record may actually not have been modified so it * might not need to be written out. + * NOTE: It is not a problem when the inode for $MFT itself is being + * written out as mark_ntfs_record_dirty() will only set I_DIRTY_PAGES + * on the $MFT inode and hence ntfs_write_inode() will not be + * re-invoked because of it which in turn is ok since the dirtied mft + * record will be cleaned and written out to disk below, i.e. before + * this function returns. */ if (modified && !NInoTestSetDirty(ctx->ntfs_ino)) - __set_page_dirty_nobuffers(ctx->ntfs_ino->page); + mark_ntfs_record_dirty(ctx->ntfs_ino, ctx->ntfs_ino->page, + ctx->ntfs_ino->page_ofs); ntfs_attr_put_search_ctx(ctx); /* Now the access times are updated, write the base mft record. */ if (NInoDirty(ni)) -- cgit v1.2.3 From bcd357a255f658880fbb5d5ff56717d9ac07cccb Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Sun, 10 Oct 2004 23:24:12 +0100 Subject: NTFS: Fix warnings on x86-64. (Randy Dunlap with slight modification from me) Fix printk arg type warnings on x86-64 (and OK on x86-32) (gcc 3.3.3): fs/ntfs/dir.c:1272: warning: long long unsigned int format, long unsigned int arg (arg 6) fs/ntfs/dir.c:1388: warning: long long unsigned int format, long unsigned int arg (arg 5 Signed-off-by: Randy Dunlap Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 2 ++ fs/ntfs/dir.c | 6 +++--- fs/ntfs/inode.c | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index f532f388ea3d..91b23be8c7db 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -68,6 +68,8 @@ ToDo/Notes: - Update fs/ntfs/inode.c::ntfs_write_inode() to also use the helper mark_ntfs_record_dirty() and thus to set the buffers belonging to the mft record dirty as well as the page itself. + - Fix compiler warnings on x86-64 in fs/ntfs/dir.c. (Randy Dunlap, + slightly modified by me) 2.1.20 - Fix two stupid bugs introduced in 2.1.18 release. diff --git a/fs/ntfs/dir.c b/fs/ntfs/dir.c index 2700e4bd5574..9b8594d4311b 100644 --- a/fs/ntfs/dir.c +++ b/fs/ntfs/dir.c @@ -1278,7 +1278,7 @@ get_next_bmp_page: ntfs_debug("Reading bitmap with page index 0x%llx, bit ofs 0x%llx", (unsigned long long)bmp_pos >> (3 + PAGE_CACHE_SHIFT), (unsigned long long)bmp_pos & - ((PAGE_CACHE_SIZE * 8) - 1)); + (unsigned long long)((PAGE_CACHE_SIZE * 8) - 1)); bmp_page = ntfs_map_page(bmp_mapping, bmp_pos >> (3 + PAGE_CACHE_SHIFT)); if (IS_ERR(bmp_page)) { @@ -1392,8 +1392,8 @@ find_next_index_buffer: */ for (;; ie = (INDEX_ENTRY*)((u8*)ie + le16_to_cpu(ie->length))) { ntfs_debug("In index allocation, offset 0x%llx.", - (unsigned long long)ia_start + ((u8*)ie - - (u8*)ia)); + (unsigned long long)ia_start + + (unsigned long long)((u8*)ie - (u8*)ia)); /* Bounds checks. */ if (unlikely((u8*)ie < (u8*)ia || (u8*)ie + sizeof(INDEX_ENTRY_HEADER) > index_end || diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index e8354aa41971..3589040b78e6 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -25,7 +25,7 @@ #include #include -#include "ntfs.h" +#include "aops.h" #include "dir.h" #include "debug.h" #include "inode.h" @@ -33,6 +33,7 @@ #include "malloc.h" #include "mft.h" #include "time.h" +#include "ntfs.h" /** * ntfs_test_inode - compare two (possibly fake) inodes for equality -- cgit v1.2.3 From 96471b7a11cff1ad1234ce32c294e556378faacb Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Mon, 11 Oct 2004 15:51:24 +0100 Subject: NTFS: Add fs/ntfs/mft.c::try_map_mft_record() which fails with -EALREADY if the mft record is already locked and otherwise behaves the same way as fs/ntfs/mft.c::map_mft_record(). Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 3 +++ fs/ntfs/mft.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ fs/ntfs/mft.h | 1 + 3 files changed, 55 insertions(+) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 91b23be8c7db..c3ff4343a74b 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -70,6 +70,9 @@ ToDo/Notes: mft record dirty as well as the page itself. - Fix compiler warnings on x86-64 in fs/ntfs/dir.c. (Randy Dunlap, slightly modified by me) + - Add fs/ntfs/mft.c::try_map_mft_record() which fails with -EALREADY if + the mft record is already locked and otherwise behaves the same way + as fs/ntfs/mft.c::map_mft_record(). 2.1.20 - Fix two stupid bugs introduced in 2.1.18 release. diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index c74ae7d72c3f..01e44140ac39 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -114,6 +114,57 @@ err_out: return (void*)page; } +/** + * try_map_mft_record - attempt to map, pin and lock an mft record + * @ni: ntfs inode whose MFT record to map + * + * First, attempt to take the mrec_lock semaphore. If the semaphore is already + * taken by someone else, return the error code -EALREADY. Otherwise continue + * as described below. + * + * The page of the record is mapped using map_mft_record_page() before being + * returned to the caller. + * + * This in turn uses ntfs_map_page() to get the page containing the wanted mft + * record (it in turn calls read_cache_page() which reads it in from disk if + * necessary, increments the use count on the page so that it cannot disappear + * under us and returns a reference to the page cache page). + * + * The mft record is now ours and we return a pointer to it. You need to check + * the returned pointer with IS_ERR() and if that is true, PTR_ERR() will return + * the error code. + * + * For further details see the description of map_mft_record() below. + */ +MFT_RECORD *try_map_mft_record(ntfs_inode *ni) +{ + MFT_RECORD *m; + + ntfs_debug("Entering for mft_no 0x%lx.", ni->mft_no); + + /* Make sure the ntfs inode doesn't go away. */ + atomic_inc(&ni->count); + + /* + * Serialize access to this mft record. If someone else is already + * holding the lock, abort instead of waiting for the lock. + */ + if (unlikely(down_trylock(&ni->mrec_lock))) { + ntfs_debug("Mft record is already locked, aborting."); + atomic_dec(&ni->count); + return ERR_PTR(-EALREADY); + } + + m = map_mft_record_page(ni); + if (likely(!IS_ERR(m))) + return m; + + up(&ni->mrec_lock); + atomic_dec(&ni->count); + ntfs_error(ni->vol->sb, "Failed with error code %lu.", -PTR_ERR(m)); + return m; +} + /** * map_mft_record - map, pin and lock an mft record * @ni: ntfs inode whose MFT record to map diff --git a/fs/ntfs/mft.h b/fs/ntfs/mft.h index 924f87abff8b..fddc92e21d90 100644 --- a/fs/ntfs/mft.h +++ b/fs/ntfs/mft.h @@ -29,6 +29,7 @@ #include "inode.h" +extern MFT_RECORD *try_map_mft_record(ntfs_inode *ni); extern MFT_RECORD *map_mft_record(ntfs_inode *ni); extern void unmap_mft_record(ntfs_inode *ni); -- cgit v1.2.3 From 565e162d810e8c7e1d86f96f65c7277596c8f7f1 Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Tue, 12 Oct 2004 00:19:53 +0100 Subject: NTFS: Modify fs/ntfs/mft.c::write_mft_record_nolock() so that it only writes the mft record if the buffers belonging to it are dirty. Otherwise we assume that it was written out by other means already. Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 3 +++ fs/ntfs/mft.c | 46 +++++++++++++++++++++++++++------------------- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index c3ff4343a74b..bc1114a099a8 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -73,6 +73,9 @@ ToDo/Notes: - Add fs/ntfs/mft.c::try_map_mft_record() which fails with -EALREADY if the mft record is already locked and otherwise behaves the same way as fs/ntfs/mft.c::map_mft_record(). + - Modify fs/ntfs/mft.c::write_mft_record_nolock() so that it only + writes the mft record if the buffers belonging to it are dirty. + Otherwise we assume that it was written out by other means already. 2.1.20 - Fix two stupid bugs introduced in 2.1.18 release. diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 01e44140ac39..32580f2f3e2e 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -678,6 +678,9 @@ err_out: * ntfs inode @ni to backing store. If the mft record @m has a counterpart in * the mft mirror, that is also updated. * + * We only write the mft record if the ntfs inode @ni is dirty and the buffers + * belonging to its mft record are dirty, too. + * * On success, clean the mft record and return 0. On error, leave the mft * record dirty and return -errno. The caller should call make_bad_inode() on * the base inode to ensure no more access happens to this inode. We do not do @@ -707,6 +710,7 @@ int write_mft_record_nolock(ntfs_inode *ni, MFT_RECORD *m, int sync) struct buffer_head *bh, *head; unsigned int block_start, block_end, m_start, m_end; int i_bhs, nr_bhs, err = 0; + BOOL rec_is_dirty = TRUE; ntfs_debug("Entering for inode 0x%lx.", ni->mft_no); BUG_ON(NInoAttr(ni)); @@ -739,29 +743,34 @@ no_buffers_err_out: do { block_end = block_start + blocksize; /* If the buffer is outside the mft record, skip it. */ - if ((block_end <= m_start) || (block_start >= m_end)) + if (block_end <= m_start) continue; - if (!buffer_mapped(bh)) { - ntfs_error(vol->sb, "Writing mft records without " - "existing mapped buffers is not " - "implemented yet. %s", - ntfs_please_email); - err = -EOPNOTSUPP; - continue; - } - if (!buffer_uptodate(bh)) { - ntfs_error(vol->sb, "Writing mft records without " - "existing uptodate buffers is not " - "implemented yet. %s", - ntfs_please_email); - err = -EOPNOTSUPP; + if (unlikely(block_start >= m_end)) + break; + /* + * If the buffer is clean and it is the first buffer of the mft + * record, it was written out by other means already so we are + * done. For safety we make sure all the other buffers are + * clean also. If it is clean but not the first buffer and the + * first buffer was dirty it is a bug. + */ + if (!buffer_dirty(bh)) { + if (block_start == m_start) + rec_is_dirty = FALSE; + else + BUG_ON(rec_is_dirty); continue; } + BUG_ON(!rec_is_dirty); + BUG_ON(!buffer_mapped(bh)); + BUG_ON(!buffer_uptodate(bh)); BUG_ON(!nr_bhs && (m_start != block_start)); BUG_ON(nr_bhs >= max_bhs); bhs[nr_bhs++] = bh; BUG_ON((nr_bhs >= max_bhs) && (m_end != block_end)); } while (block_start = block_end, (bh = bh->b_this_page) != head); + if (!rec_is_dirty) + goto done; if (unlikely(err)) goto cleanup_out; /* Apply the mst protection fixups. */ @@ -778,8 +787,7 @@ no_buffers_err_out: if (unlikely(test_set_buffer_locked(tbh))) BUG(); BUG_ON(!buffer_uptodate(tbh)); - if (buffer_dirty(tbh)) - clear_buffer_dirty(tbh); + clear_buffer_dirty(tbh); get_bh(tbh); tbh->b_end_io = end_buffer_write_sync; submit_bh(WRITE, tbh); @@ -795,8 +803,8 @@ no_buffers_err_out: if (unlikely(!buffer_uptodate(tbh))) { err = -EIO; /* - * Set the buffer uptodate so the page & buffer states - * don't become out of sync. + * Set the buffer uptodate so the page and buffer + * states do not become out of sync. */ if (PageUptodate(page)) set_buffer_uptodate(tbh); -- cgit v1.2.3 From 8b393108004edaf145e6abf5ad3c3a9950c4db78 Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Tue, 12 Oct 2004 11:09:27 +0100 Subject: NTFS: Attempting to write outside initialized size is _not_ a bug so remove the bug check from fs/ntfs/aops.c::ntfs_write_mst_block(). It is in fact required to write outside initialized size when preparing to extend the initialized size. Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 4 ++++ fs/ntfs/aops.c | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index bc1114a099a8..bbd8335ba83d 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -76,6 +76,10 @@ ToDo/Notes: - Modify fs/ntfs/mft.c::write_mft_record_nolock() so that it only writes the mft record if the buffers belonging to it are dirty. Otherwise we assume that it was written out by other means already. + - Attempting to write outside initialized size is _not_ a bug so remove + the bug check from fs/ntfs/aops.c::ntfs_write_mst_block(). It is in + fact required to write outside initialized size when preparing to + extend the initialized size. 2.1.20 - Fix two stupid bugs introduced in 2.1.18 release. diff --git a/fs/ntfs/aops.c b/fs/ntfs/aops.c index 61da8d51fd6e..6070fe0341b5 100644 --- a/fs/ntfs/aops.c +++ b/fs/ntfs/aops.c @@ -892,8 +892,6 @@ no_buffers_err_out: } BUG_ON(!rec_is_dirty); } - /* Attempting to write outside the initialized size is a bug. */ - BUG_ON(((block + 1) << bh_size_bits) > ni->initialized_size); if (!buffer_mapped(bh)) { ntfs_error(vol->sb, "Writing ntfs records without " "existing mapped buffers is not " -- cgit v1.2.3 From 3e593cf74a35e04bd556af681965fd90b96c47df Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Tue, 12 Oct 2004 11:21:04 +0100 Subject: NTFS: Map the page instead of using page_address() before writing to it in fs/ntfs/aops.c::ntfs_mft_writepage(). Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 2 ++ fs/ntfs/aops.c | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index bbd8335ba83d..a3ded99e9a02 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -80,6 +80,8 @@ ToDo/Notes: the bug check from fs/ntfs/aops.c::ntfs_write_mst_block(). It is in fact required to write outside initialized size when preparing to extend the initialized size. + - Map the page instead of using page_address() before writing to it in + fs/ntfs/aops.c::ntfs_mft_writepage(). 2.1.20 - Fix two stupid bugs introduced in 2.1.18 release. diff --git a/fs/ntfs/aops.c b/fs/ntfs/aops.c index 6070fe0341b5..e3fb4ef6f362 100644 --- a/fs/ntfs/aops.c +++ b/fs/ntfs/aops.c @@ -917,7 +917,7 @@ no_buffers_err_out: if (!nr_bhs) goto done; /* Apply the mst protection fixups. */ - kaddr = page_address(page); + kaddr = kmap(page); for (i = 0; i < nr_bhs; i++) { if (!(i % bhs_per_rec)) { err = pre_write_mst_fixup((NTFS_RECORD*)(kaddr + @@ -974,6 +974,7 @@ no_buffers_err_out: bh_offset(bhs[i]))); } flush_dcache_page(page); + kunmap(page); if (unlikely(err)) { /* I/O error during writing. This is really bad! */ ntfs_error(vol->sb, "I/O error while writing ntfs record " @@ -996,6 +997,7 @@ mst_cleanup_out: post_write_mst_fixup((NTFS_RECORD*)(kaddr + bh_offset(bhs[i]))); } + kunmap(page); cleanup_out: /* Clean the buffers. */ for (i = 0; i < nr_bhs; i++) -- cgit v1.2.3 From efd012db6d58f541c417607595c1394c2ed65cd7 Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Tue, 12 Oct 2004 11:27:35 +0100 Subject: NTFS: Provide exclusion between opening an inode / mapping an mft record and accessing the mft record in fs/ntfs/mft.c::ntfs_mft_writepage() by setting the page not uptodate throughout ntfs_mft_writepage(). Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 3 +++ fs/ntfs/mft.c | 20 +++++++++----------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index a3ded99e9a02..a59525b28152 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -82,6 +82,9 @@ ToDo/Notes: extend the initialized size. - Map the page instead of using page_address() before writing to it in fs/ntfs/aops.c::ntfs_mft_writepage(). + - Provide exclusion between opening an inode / mapping an mft record + and accessing the mft record in fs/ntfs/mft.c::ntfs_mft_writepage() + by setting the page not uptodate throughout ntfs_mft_writepage(). 2.1.20 - Fix two stupid bugs introduced in 2.1.18 release. diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 32580f2f3e2e..60ffcb1a1221 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -724,18 +724,9 @@ int write_mft_record_nolock(ntfs_inode *ni, MFT_RECORD *m, int sync) */ if (!NInoTestClearDirty(ni)) goto done; - /* Make sure we have mapped buffers. */ - if (!page_has_buffers(page)) { -no_buffers_err_out: - ntfs_error(vol->sb, "Writing mft records without existing " - "buffers is not implemented yet. %s", - ntfs_please_email); - err = -EOPNOTSUPP; - goto err_out; - } + BUG_ON(!page_has_buffers(page)); bh = head = page_buffers(page); - if (!bh) - goto no_buffers_err_out; + BUG_ON(!bh); nr_bhs = 0; block_start = 0; m_start = ni->page_ofs; @@ -892,6 +883,12 @@ static int ntfs_mft_writepage(struct page *page, struct writeback_control *wbc) ntfs_debug("Entering for %i inodes starting at 0x%lx.", nr, mft_no); /* Iterate over the mft records in the page looking for a dirty one. */ maddr = (u8*)kmap(page); + /* + * Clear the page uptodate flag. This will cause anyone trying to get + * hold of the page to block on the page lock in read_cache_page(). + */ + BUG_ON(!PageUptodate(page)); + ClearPageUptodate(page); for (i = 0; i < nr; ++i, ++mft_no, maddr += vol->mft_record_size) { struct inode *vi; ntfs_inode *ni, *eni; @@ -1034,6 +1031,7 @@ static int ntfs_mft_writepage(struct page *page, struct writeback_control *wbc) up(&ni->extent_lock); iput(vi); } + SetPageUptodate(page); kunmap(page); /* If a dirty mft record was found, redirty the page. */ if (is_dirty) { -- cgit v1.2.3 From d7ca9266c15225cdaa8a34e0938ac622df707111 Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Thu, 14 Oct 2004 19:02:47 +0100 Subject: NTFS: Big cleanup of mft record writing code. - Clear the page uptodate flag in fs/ntfs/aops.c::ntfs_write_mst_block() to ensure noone can see the page whilst the mst fixups are applied. - Add the helper fs/ntfs/mft.c::ntfs_may_write_mft_record() which checks if an mft record may be written out safely obtaining any necessary locks in the process. This is used by fs/ntfs/aops.c::ntfs_write_mst_block(). - Modify fs/ntfs/aops.c::ntfs_write_mst_block() to also work for writing mft records and improve its error handling in the process. Now if any of the records in the page fail to be written out, all other records will be written out instead of aborting completely. - Remove ntfs_mft_aops and update all users to use ntfs_mst_aops. - Modify fs/ntfs/inode.c::ntfs_read_locked_inode() to set the ntfs_mst_aops for all inodes which are NInoMstProtected() and ntfs_aops for all other inodes. - Rename fs/ntfs/mft.c::sync_mft_mirror{,_umount}() to ntfs_sync_mft_mirror{,_umount}() and change their parameters so they no longer require an ntfs inode to be present. Update all callers. - Cleanup the error handling in fs/ntfs/mft.c::ntfs_sync_mft_mirror(). - Clear the page uptodate flag in fs/ntfs/mft.c::ntfs_sync_mft_mirror() to ensure noone can see the page whilst the mst fixups are applied. - Remove the no longer needed fs/ntfs/mft.c::ntfs_mft_writepage() and fs/ntfs/mft.c::try_map_mft_record(). - Fix callers of fs/ntfs/aops.c::mark_ntfs_record_dirty() to call it with the ntfs inode which contains the page rather than the ntfs inode the mft record of which is in the page. Ooops. Yes, I know, I should have split this up into smaller changes... Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 25 +++ fs/ntfs/aops.c | 304 +++++++++++++++++---------- fs/ntfs/inode.c | 14 +- fs/ntfs/mft.c | 611 ++++++++++++++++++++++++------------------------------ fs/ntfs/mft.h | 8 +- fs/ntfs/ntfs.h | 1 - fs/ntfs/super.c | 4 +- 7 files changed, 511 insertions(+), 456 deletions(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index a59525b28152..6217c5b7c2c4 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -85,6 +85,31 @@ ToDo/Notes: - Provide exclusion between opening an inode / mapping an mft record and accessing the mft record in fs/ntfs/mft.c::ntfs_mft_writepage() by setting the page not uptodate throughout ntfs_mft_writepage(). + - Clear the page uptodate flag in fs/ntfs/aops.c::ntfs_write_mst_block() + to ensure noone can see the page whilst the mst fixups are applied. + - Add the helper fs/ntfs/mft.c::ntfs_may_write_mft_record() which + checks if an mft record may be written out safely obtaining any + necessary locks in the process. This is used by + fs/ntfs/aops.c::ntfs_write_mst_block(). + - Modify fs/ntfs/aops.c::ntfs_write_mst_block() to also work for + writing mft records and improve its error handling in the process. + Now if any of the records in the page fail to be written out, all + other records will be written out instead of aborting completely. + - Remove ntfs_mft_aops and update all users to use ntfs_mst_aops. + - Modify fs/ntfs/inode.c::ntfs_read_locked_inode() to set the + ntfs_mst_aops for all inodes which are NInoMstProtected() and + ntfs_aops for all other inodes. + - Rename fs/ntfs/mft.c::sync_mft_mirror{,_umount}() to + ntfs_sync_mft_mirror{,_umount}() and change their parameters so they + no longer require an ntfs inode to be present. Update all callers. + - Cleanup the error handling in fs/ntfs/mft.c::ntfs_sync_mft_mirror(). + - Clear the page uptodate flag in fs/ntfs/mft.c::ntfs_sync_mft_mirror() + to ensure noone can see the page whilst the mst fixups are applied. + - Remove the no longer needed fs/ntfs/mft.c::ntfs_mft_writepage() and + fs/ntfs/mft.c::try_map_mft_record(). + - Fix callers of fs/ntfs/aops.c::mark_ntfs_record_dirty() to call it + with the ntfs inode which contains the page rather than the ntfs + inode the mft record of which is in the page. 2.1.20 - Fix two stupid bugs introduced in 2.1.18 release. diff --git a/fs/ntfs/aops.c b/fs/ntfs/aops.c index e3fb4ef6f362..ede1c42b8cbf 100644 --- a/fs/ntfs/aops.c +++ b/fs/ntfs/aops.c @@ -26,6 +26,7 @@ #include #include #include +#include #include "aops.h" #include "debug.h" @@ -777,25 +778,25 @@ lock_retry_remap: return err; } -static const char *ntfs_please_email = "Please email " - "linux-ntfs-dev@lists.sourceforge.net and say that you saw " - "this message. Thank you."; - /** * ntfs_write_mst_block - write a @page to the backing store * @wbc: writeback control structure * @page: page cache page to write out * * This function is for writing pages belonging to non-resident, mst protected - * attributes to their backing store. The only supported attribute is the - * index allocation attribute. Both directory inodes and index inodes are - * supported. + * attributes to their backing store. The only supported attributes are index + * allocation and $MFT/$DATA. Both directory inodes and index inodes are + * supported for the index allocation case. * * The page must remain locked for the duration of the write because we apply * the mst fixups, write, and then undo the fixups, so if we were to unlock the * page before undoing the fixups, any other user of the page will see the * page contents as corrupt. * + * We clear the page uptodate flag for the duration of the function to ensure + * exclusion for the $MFT/$DATA case against someone mapping an mft record we + * are about to apply the mst fixups to. + * * Return 0 on success and -errno on error. * * Based on ntfs_write_block(), ntfs_mft_writepage(), and @@ -810,60 +811,53 @@ static int ntfs_write_mst_block(struct writeback_control *wbc, ntfs_volume *vol = ni->vol; u8 *kaddr; unsigned int bh_size = 1 << vi->i_blkbits; - unsigned int rec_size; - struct buffer_head *bh, *head; + unsigned int rec_size = ni->itype.index.block_size; + ntfs_inode *locked_nis[PAGE_CACHE_SIZE / rec_size]; + struct buffer_head *bh, *head, *tbh; int max_bhs = PAGE_CACHE_SIZE / bh_size; struct buffer_head *bhs[max_bhs]; - int i, nr_recs, nr_bhs, bhs_per_rec, err; - unsigned char bh_size_bits; - BOOL rec_is_dirty; + int i, nr_locked_nis, nr_recs, nr_bhs, bhs_per_rec, err; + unsigned char bh_size_bits, rec_size_bits; + BOOL sync, is_mft, page_is_dirty, rec_is_dirty; ntfs_debug("Entering for inode 0x%lx, attribute type 0x%x, page index " "0x%lx.", vi->i_ino, ni->type, page->index); BUG_ON(!NInoNonResident(ni)); BUG_ON(!NInoMstProtected(ni)); - BUG_ON(!(S_ISDIR(vi->i_mode) || + is_mft = (S_ISREG(vi->i_mode) && !vi->i_ino); + BUG_ON(!(is_mft || S_ISDIR(vi->i_mode) || (NInoAttr(ni) && ni->type == AT_INDEX_ALLOCATION))); - BUG_ON(PageWriteback(page)); - BUG_ON(!PageUptodate(page)); BUG_ON(!max_bhs); + /* Were we called for sync purposes? */ + sync = (wbc->sync_mode == WB_SYNC_ALL); + /* Make sure we have mapped buffers. */ - if (unlikely(!page_has_buffers(page))) { -no_buffers_err_out: - ntfs_error(vol->sb, "Writing ntfs records without existing " - "buffers is not implemented yet. %s", - ntfs_please_email); - err = -EOPNOTSUPP; - goto err_out; - } + BUG_ON(!page_has_buffers(page)); bh = head = page_buffers(page); - if (unlikely(!bh)) - goto no_buffers_err_out; + BUG_ON(!bh); bh_size_bits = vi->i_blkbits; - rec_size = ni->itype.index.block_size; - nr_recs = PAGE_CACHE_SIZE / rec_size; - BUG_ON(!nr_recs); + rec_size_bits = ni->itype.index.block_size_bits; + BUG_ON(!(PAGE_CACHE_SIZE >> rec_size_bits)); bhs_per_rec = rec_size >> bh_size_bits; BUG_ON(!bhs_per_rec); /* The first block in the page. */ - rec_block = block = (s64)page->index << + rec_block = block = (sector_t)page->index << (PAGE_CACHE_SHIFT - bh_size_bits); /* The first out of bounds block for the data size. */ dblock = (vi->i_size + bh_size - 1) >> bh_size_bits; - err = nr_bhs = 0; - /* Need this to silence a stupid gcc warning. */ - rec_is_dirty = FALSE; + err = nr_bhs = nr_recs = nr_locked_nis = 0; + page_is_dirty = rec_is_dirty = FALSE; do { if (unlikely(block >= dblock)) { /* * Mapped buffers outside i_size will occur, because * this page can be outside i_size when there is a - * truncate in progress. The contents of such buffers + * truncate in progress. The contents of such buffers * were zeroed by ntfs_writepage(). * * FIXME: What about the small race window where @@ -876,7 +870,7 @@ no_buffers_err_out: } if (rec_block == block) { /* This block is the first one in the record. */ - rec_block += rec_size >> bh_size_bits; + rec_block += bhs_per_rec; if (!buffer_dirty(bh)) { /* Clean buffers are not written out. */ rec_is_dirty = FALSE; @@ -892,54 +886,91 @@ no_buffers_err_out: } BUG_ON(!rec_is_dirty); } - if (!buffer_mapped(bh)) { - ntfs_error(vol->sb, "Writing ntfs records without " - "existing mapped buffers is not " - "implemented yet. %s", - ntfs_please_email); - clear_buffer_dirty(bh); - err = -EOPNOTSUPP; - goto cleanup_out; - } - if (!buffer_uptodate(bh)) { - ntfs_error(vol->sb, "Writing ntfs records without " - "existing uptodate buffers is not " - "implemented yet. %s", - ntfs_please_email); - clear_buffer_dirty(bh); - err = -EOPNOTSUPP; - goto cleanup_out; - } + BUG_ON(!buffer_mapped(bh)); + BUG_ON(!buffer_uptodate(bh)); bhs[nr_bhs++] = bh; BUG_ON(nr_bhs > max_bhs); } while (block++, (bh = bh->b_this_page) != head); /* If there were no dirty buffers, we are done. */ if (!nr_bhs) goto done; - /* Apply the mst protection fixups. */ + /* Map the page so we can access its contents. */ kaddr = kmap(page); + /* Clear the page uptodate flag whilst the mst fixups are applied. */ + BUG_ON(!PageUptodate(page)); + ClearPageUptodate(page); for (i = 0; i < nr_bhs; i++) { - if (!(i % bhs_per_rec)) { - err = pre_write_mst_fixup((NTFS_RECORD*)(kaddr + - bh_offset(bhs[i])), rec_size); - if (err) { - ntfs_error(vol->sb, "Failed to apply mst " - "fixups (inode 0x%lx, " - "attribute type 0x%x, page " - "index 0x%lx)! Umount and " - "run chkdsk.", vi->i_ino, - ni->type, - page->index); - nr_bhs = i; - goto mst_cleanup_out; + unsigned int ofs; + + /* Skip buffers which are not at the beginning of records. */ + if (i % bhs_per_rec) + continue; + tbh = bhs[i]; + ofs = bh_offset(tbh); + if (is_mft) { + ntfs_inode *tni; + unsigned long mft_no; + + /* Get the mft record number. */ + mft_no = (((s64)page->index << PAGE_CACHE_SHIFT) + ofs) + >> rec_size_bits; + /* Check whether to write this mft record. */ + tni = NULL; + if (!ntfs_may_write_mft_record(vol, mft_no, + (MFT_RECORD*)(kaddr + ofs), &tni)) { + /* + * The record should not be written. This + * means we need to redirty the page before + * returning. + */ + page_is_dirty = TRUE; + /* + * Remove the buffers in this mft record from + * the list of buffers to write. + */ + do { + bhs[i] = NULL; + } while (++i % bhs_per_rec); + continue; } + /* + * The record should be written. If a locked ntfs + * inode was returned, add it to the array of locked + * ntfs inodes. + */ + if (tni) + locked_nis[nr_locked_nis++] = tni; + } + /* Apply the mst protection fixups. */ + err = pre_write_mst_fixup((NTFS_RECORD*)(kaddr + ofs), + rec_size); + if (unlikely(err)) { + ntfs_error(vol->sb, "Failed to apply mst fixups " + "(inode 0x%lx, attribute type 0x%x, " + "page index 0x%lx, page offset 0x%x)!" + " Unmount and run chkdsk.", vi->i_ino, + ni->type, page->index, ofs); + /* + * Mark all the buffers in this record clean as we do + * not want to write corrupt data to disk. + */ + do { + clear_buffer_dirty(bhs[i]); + bhs[i] = NULL; + } while (++i % bhs_per_rec); + continue; } + nr_recs++; } + /* If no records are to be written out, we are done. */ + if (!nr_recs) + goto unm_done; flush_dcache_page(page); /* Lock buffers and start synchronous write i/o on them. */ for (i = 0; i < nr_bhs; i++) { - struct buffer_head *tbh = bhs[i]; - + tbh = bhs[i]; + if (!tbh) + continue; if (unlikely(test_set_buffer_locked(tbh))) BUG(); if (unlikely(!test_clear_buffer_dirty(tbh))) { @@ -952,59 +983,121 @@ no_buffers_err_out: tbh->b_end_io = end_buffer_write_sync; submit_bh(WRITE, tbh); } + /* Synchronize the mft mirror now if not @sync. */ + if (is_mft && !sync) + goto do_mirror; +do_wait: /* Wait on i/o completion of buffers. */ for (i = 0; i < nr_bhs; i++) { - struct buffer_head *tbh = bhs[i]; - + tbh = bhs[i]; + if (!tbh) + continue; wait_on_buffer(tbh); if (unlikely(!buffer_uptodate(tbh))) { + ntfs_error(vol->sb, "I/O error while writing ntfs " + "record buffer (inode 0x%lx, " + "attribute type 0x%x, page index " + "0x%lx, page offset 0x%lx)! Unmount " + "and run chkdsk.", vi->i_ino, ni->type, + page->index, bh_offset(tbh)); err = -EIO; /* - * Set the buffer uptodate so the page & buffer states - * don't become out of sync. + * Set the buffer uptodate so the page and buffer + * states do not become out of sync. */ - if (PageUptodate(page)) - set_buffer_uptodate(tbh); + set_buffer_uptodate(tbh); } } + /* If @sync, now synchronize the mft mirror. */ + if (is_mft && sync) { +do_mirror: + for (i = 0; i < nr_bhs; i++) { + unsigned long mft_no; + unsigned int ofs; + + /* + * Skip buffers which are not at the beginning of + * records. + */ + if (i % bhs_per_rec) + continue; + tbh = bhs[i]; + /* Skip removed buffers (and hence records). */ + if (!tbh) + continue; + ofs = bh_offset(tbh); + /* Get the mft record number. */ + mft_no = (((s64)page->index << PAGE_CACHE_SHIFT) + ofs) + >> rec_size_bits; + if (mft_no < vol->mftmirr_size) + ntfs_sync_mft_mirror(vol, mft_no, + (MFT_RECORD*)(kaddr + ofs), + sync); + } + if (!sync) + goto do_wait; + } /* Remove the mst protection fixups again. */ for (i = 0; i < nr_bhs; i++) { - if (!(i % bhs_per_rec)) + if (!(i % bhs_per_rec)) { + tbh = bhs[i]; + if (!tbh) + continue; post_write_mst_fixup((NTFS_RECORD*)(kaddr + - bh_offset(bhs[i]))); + bh_offset(tbh))); + } } flush_dcache_page(page); - kunmap(page); +unm_done: + /* Unlock any locked inodes. */ + while (nr_locked_nis-- > 0) { + ntfs_inode *tni, *base_tni; + + tni = locked_nis[nr_locked_nis]; + /* Get the base inode. */ + down(&tni->extent_lock); + if (tni->nr_extents >= 0) + base_tni = tni; + else { + base_tni = tni->ext.base_ntfs_ino; + BUG_ON(!base_tni); + } + up(&tni->extent_lock); + ntfs_debug("Unlocking %s inode 0x%lx.", + tni == base_tni ? "base" : "extent", + tni->mft_no); + up(&tni->mrec_lock); + atomic_dec(&tni->count); + iput(VFS_I(base_tni)); + } if (unlikely(err)) { - /* I/O error during writing. This is really bad! */ - ntfs_error(vol->sb, "I/O error while writing ntfs record " - "(inode 0x%lx, attribute type 0x%x, page " - "index 0x%lx)! Umount and run chkdsk.", - vi->i_ino, ni->type, page->index); - goto err_out; + SetPageError(page); + NVolSetErrors(vol); } + SetPageUptodate(page); + kunmap(page); done: - set_page_writeback(page); - unlock_page(page); - end_page_writeback(page); - if (!err) + if (page_is_dirty) { + ntfs_debug("Page still contains one or more dirty ntfs " + "records. Redirtying the page starting at " + "record 0x%lx.", page->index << + (PAGE_CACHE_SHIFT - rec_size_bits)); + redirty_page_for_writepage(wbc, page); + unlock_page(page); + } else { + /* + * Keep the VM happy. This must be done otherwise the + * radix-tree tag PAGECACHE_TAG_DIRTY remains set even though + * the page is clean. + */ + BUG_ON(PageWriteback(page)); + set_page_writeback(page); + unlock_page(page); + end_page_writeback(page); + } + if (likely(!err)) ntfs_debug("Done."); return err; -mst_cleanup_out: - /* Remove the mst protection fixups again. */ - for (i = 0; i < nr_bhs; i++) { - if (!(i % bhs_per_rec)) - post_write_mst_fixup((NTFS_RECORD*)(kaddr + - bh_offset(bhs[i]))); - } - kunmap(page); -cleanup_out: - /* Clean the buffers. */ - for (i = 0; i < nr_bhs; i++) - clear_buffer_dirty(bhs[i]); -err_out: - SetPageError(page); - goto done; } /** @@ -1012,6 +1105,9 @@ err_out: * @page: page cache page to write out * @wbc: writeback control structure * + * This is called from the VM when it wants to have a dirty ntfs page cache + * page cleaned. The VM has already locked the page and marked it clean. + * * For non-resident attributes, ntfs_writepage() writes the @page by calling * the ntfs version of the generic block_write_full_page() function, * ntfs_write_block(), which in turn if necessary creates and writes the @@ -1022,8 +1118,6 @@ err_out: * The mft record is then marked dirty and written out asynchronously via the * vfs inode dirty code path. * - * Note the caller clears the page dirty flag before calling ntfs_writepage(). - * * Based on ntfs_readpage() and fs/buffer.c::block_write_full_page(). * * Return 0 on success and -errno on error. @@ -2038,7 +2132,7 @@ struct address_space_operations ntfs_mst_aops = { /** * mark_ntfs_record_dirty - mark an ntfs record dirty - * @ni: ntfs inode to which the ntfs record to be marked dirty belongs + * @ni: ntfs inode containing the ntfs record to be marked dirty * @page: page containing the ntfs record to mark dirty * @rec_start: byte offset within @page at which the ntfs record begins * diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 3589040b78e6..11032d239103 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -968,7 +968,6 @@ skip_large_dir_stuff: /* Setup the operations for this inode. */ vi->i_op = &ntfs_dir_inode_ops; vi->i_fop = &ntfs_dir_ops; - vi->i_mapping->a_ops = &ntfs_mst_aops; } else { /* It is a file. */ ntfs_attr_reinit_search_ctx(ctx); @@ -1112,8 +1111,11 @@ no_data_attr_special_case: /* Setup the operations for this inode. */ vi->i_op = &ntfs_file_inode_ops; vi->i_fop = &ntfs_file_ops; - vi->i_mapping->a_ops = &ntfs_aops; } + if (NInoMstProtected(ni)) + vi->i_mapping->a_ops = &ntfs_mst_aops; + else + vi->i_mapping->a_ops = &ntfs_aops; /* * The number of 512-byte blocks used on disk (for stat). This is in so * far inaccurate as it doesn't account for any named streams or other @@ -1766,7 +1768,7 @@ int ntfs_read_inode_mount(struct inode *vi) vi->i_generation = ni->seq_no = le16_to_cpu(m->sequence_number); /* Provides readpage() and sync_page() for map_mft_record(). */ - vi->i_mapping->a_ops = &ntfs_mft_aops; + vi->i_mapping->a_ops = &ntfs_mst_aops; ctx = ntfs_attr_get_search_ctx(ni, m); if (!ctx) { @@ -2028,8 +2030,6 @@ int ntfs_read_inode_mount(struct inode *vi) /* No VFS initiated operations allowed for $MFT. */ vi->i_op = &ntfs_empty_inode_ops; vi->i_fop = &ntfs_empty_file_ops; - /* Put back our special address space operations. */ - vi->i_mapping->a_ops = &ntfs_mft_aops; } /* Get the lowest vcn for the next extent. */ @@ -2514,8 +2514,8 @@ int ntfs_write_inode(struct inode *vi, int sync) * this function returns. */ if (modified && !NInoTestSetDirty(ctx->ntfs_ino)) - mark_ntfs_record_dirty(ctx->ntfs_ino, ctx->ntfs_ino->page, - ctx->ntfs_ino->page_ofs); + mark_ntfs_record_dirty(NTFS_I(ni->vol->mft_ino), + ctx->ntfs_ino->page, ctx->ntfs_ino->page_ofs); ntfs_attr_put_search_ctx(ctx); /* Now the access times are updated, write the base mft record. */ if (NInoDirty(ni)) diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 60ffcb1a1221..757effcd7066 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -31,37 +31,6 @@ #include "malloc.h" #include "ntfs.h" -/** - * ntfs_readpage - external declaration, function is in fs/ntfs/aops.c - */ -extern int ntfs_readpage(struct file *, struct page *); - -#ifdef NTFS_RW -/** - * ntfs_mft_writepage - forward declaration, function is further below - */ -static int ntfs_mft_writepage(struct page *page, struct writeback_control *wbc); -#endif /* NTFS_RW */ - -/** - * ntfs_mft_aops - address space operations for access to $MFT - * - * Address space operations for access to $MFT. This allows us to simply use - * ntfs_map_page() in map_mft_record_page(). - */ -struct address_space_operations ntfs_mft_aops = { - .readpage = ntfs_readpage, /* Fill page with data. */ - .sync_page = block_sync_page, /* Currently, just unplugs the - disk request queue. */ -#ifdef NTFS_RW - .writepage = ntfs_mft_writepage, /* Write out the dirty mft - records in a page. */ - .set_page_dirty = __set_page_dirty_nobuffers, /* Set the page dirty - without touching the buffers - belonging to the page. */ -#endif /* NTFS_RW */ -}; - /** * map_mft_record_page - map the page in which a specific mft record resides * @ni: ntfs inode whose mft record page to map @@ -114,57 +83,6 @@ err_out: return (void*)page; } -/** - * try_map_mft_record - attempt to map, pin and lock an mft record - * @ni: ntfs inode whose MFT record to map - * - * First, attempt to take the mrec_lock semaphore. If the semaphore is already - * taken by someone else, return the error code -EALREADY. Otherwise continue - * as described below. - * - * The page of the record is mapped using map_mft_record_page() before being - * returned to the caller. - * - * This in turn uses ntfs_map_page() to get the page containing the wanted mft - * record (it in turn calls read_cache_page() which reads it in from disk if - * necessary, increments the use count on the page so that it cannot disappear - * under us and returns a reference to the page cache page). - * - * The mft record is now ours and we return a pointer to it. You need to check - * the returned pointer with IS_ERR() and if that is true, PTR_ERR() will return - * the error code. - * - * For further details see the description of map_mft_record() below. - */ -MFT_RECORD *try_map_mft_record(ntfs_inode *ni) -{ - MFT_RECORD *m; - - ntfs_debug("Entering for mft_no 0x%lx.", ni->mft_no); - - /* Make sure the ntfs inode doesn't go away. */ - atomic_inc(&ni->count); - - /* - * Serialize access to this mft record. If someone else is already - * holding the lock, abort instead of waiting for the lock. - */ - if (unlikely(down_trylock(&ni->mrec_lock))) { - ntfs_debug("Mft record is already locked, aborting."); - atomic_dec(&ni->count); - return ERR_PTR(-EALREADY); - } - - m = map_mft_record_page(ni); - if (likely(!IS_ERR(m))) - return m; - - up(&ni->mrec_lock); - atomic_dec(&ni->count); - ntfs_error(ni->vol->sb, "Failed with error code %lu.", -PTR_ERR(m)); - return m; -} - /** * map_mft_record - map, pin and lock an mft record * @ni: ntfs inode whose MFT record to map @@ -462,7 +380,8 @@ void __mark_mft_record_dirty(ntfs_inode *ni) ntfs_debug("Entering for inode 0x%lx.", ni->mft_no); BUG_ON(NInoAttr(ni)); - mark_ntfs_record_dirty(ni, ni->page, ni->page_ofs); + mark_ntfs_record_dirty(NTFS_I(ni->vol->mft_ino), ni->page, + ni->page_ofs); /* Determine the base vfs inode and mark it dirty, too. */ down(&ni->extent_lock); if (likely(ni->nr_extents >= 0)) @@ -478,13 +397,14 @@ static const char *ntfs_please_email = "Please email " "this message. Thank you."; /** - * sync_mft_mirror_umount - synchronise an mft record to the mft mirror - * @ni: ntfs inode whose mft record to synchronize + * ntfs_sync_mft_mirror_umount - synchronise an mft record to the mft mirror + * @vol: ntfs volume on which the mft record to synchronize resides + * @mft_no: mft record number of mft record to synchronize * @m: mapped, mst protected (extent) mft record to synchronize * - * Write the mapped, mst protected (extent) mft record @m described by the - * (regular or extent) ntfs inode @ni to the mft mirror ($MFTMirr) bypassing - * the page cache and the $MFTMirr inode itself. + * Write the mapped, mst protected (extent) mft record @m with mft record + * number @mft_no to the mft mirror ($MFTMirr) of the ntfs volume @vol, + * bypassing the page cache and the $MFTMirr inode itself. * * This function is only for use at umount time when the mft mirror inode has * already been disposed off. We BUG() if we are called while the mft mirror @@ -498,10 +418,9 @@ static const char *ntfs_please_email = "Please email " * alternative would be either to BUG() or to get a NULL pointer dereference * and Oops. */ -static int sync_mft_mirror_umount(ntfs_inode *ni, MFT_RECORD *m) +static int ntfs_sync_mft_mirror_umount(ntfs_volume *vol, + const unsigned long mft_no, MFT_RECORD *m) { - ntfs_volume *vol = ni->vol; - BUG_ON(vol->mftmirr_ino); ntfs_error(vol->sb, "Umount time mft mirror syncing is not " "implemented yet. %s", ntfs_please_email); @@ -509,25 +428,26 @@ static int sync_mft_mirror_umount(ntfs_inode *ni, MFT_RECORD *m) } /** - * sync_mft_mirror - synchronize an mft record to the mft mirror - * @ni: ntfs inode whose mft record to synchronize + * ntfs_sync_mft_mirror - synchronize an mft record to the mft mirror + * @vol: ntfs volume on which the mft record to synchronize resides + * @mft_no: mft record number of mft record to synchronize * @m: mapped, mst protected (extent) mft record to synchronize * @sync: if true, wait for i/o completion * - * Write the mapped, mst protected (extent) mft record @m described by the - * (regular or extent) ntfs inode @ni to the mft mirror ($MFTMirr). + * Write the mapped, mst protected (extent) mft record @m with mft record + * number @mft_no to the mft mirror ($MFTMirr) of the ntfs volume @vol. * * On success return 0. On error return -errno and set the volume errors flag - * in the ntfs_volume to which @ni belongs. + * in the ntfs volume @vol. * * NOTE: We always perform synchronous i/o and ignore the @sync parameter. * * TODO: If @sync is false, want to do truly asynchronous i/o, i.e. just * schedule i/o via ->writepage or do it via kntfsd or whatever. */ -static int sync_mft_mirror(ntfs_inode *ni, MFT_RECORD *m, int sync) +int ntfs_sync_mft_mirror(ntfs_volume *vol, const unsigned long mft_no, + MFT_RECORD *m, int sync) { - ntfs_volume *vol = ni->vol; struct page *page; unsigned int blocksize = vol->sb->s_blocksize; int max_bhs = vol->mft_record_size / blocksize; @@ -537,17 +457,17 @@ static int sync_mft_mirror(ntfs_inode *ni, MFT_RECORD *m, int sync) unsigned int block_start, block_end, m_start, m_end; int i_bhs, nr_bhs, err = 0; - ntfs_debug("Entering for inode 0x%lx.", ni->mft_no); + ntfs_debug("Entering for inode 0x%lx.", mft_no); BUG_ON(!max_bhs); if (unlikely(!vol->mftmirr_ino)) { /* This could happen during umount... */ - err = sync_mft_mirror_umount(ni, m); + err = ntfs_sync_mft_mirror_umount(vol, mft_no, m); if (likely(!err)) return err; goto err_out; } /* Get the page containing the mirror copy of the mft record @m. */ - page = ntfs_map_page(vol->mftmirr_ino->i_mapping, ni->mft_no >> + page = ntfs_map_page(vol->mftmirr_ino->i_mapping, mft_no >> (PAGE_CACHE_SHIFT - vol->mft_record_size_bits)); if (IS_ERR(page)) { ntfs_error(vol->sb, "Failed to map mft mirror page."); @@ -561,23 +481,17 @@ static int sync_mft_mirror(ntfs_inode *ni, MFT_RECORD *m, int sync) * make sure no one is writing from elsewhere. */ lock_page(page); + BUG_ON(!PageUptodate(page)); + ClearPageUptodate(page); /* The address in the page of the mirror copy of the mft record @m. */ - kmirr = page_address(page) + ((ni->mft_no << vol->mft_record_size_bits) - & ~PAGE_CACHE_MASK); + kmirr = page_address(page) + ((mft_no << vol->mft_record_size_bits) & + ~PAGE_CACHE_MASK); /* Copy the mst protected mft record to the mirror. */ memcpy(kmirr, m, vol->mft_record_size); /* Make sure we have mapped buffers. */ - if (!page_has_buffers(page)) { -no_buffers_err_out: - ntfs_error(vol->sb, "Writing mft mirror records without " - "existing buffers is not implemented yet. %s", - ntfs_please_email); - err = -EOPNOTSUPP; - goto unlock_err_out; - } + BUG_ON(!page_has_buffers(page)); bh = head = page_buffers(page); - if (!bh) - goto no_buffers_err_out; + BUG_ON(!bh); nr_bhs = 0; block_start = 0; m_start = kmirr - (u8*)page_address(page); @@ -587,22 +501,8 @@ no_buffers_err_out: /* If the buffer is outside the mft record, skip it. */ if ((block_end <= m_start) || (block_start >= m_end)) continue; - if (!buffer_mapped(bh)) { - ntfs_error(vol->sb, "Writing mft mirror records " - "without existing mapped buffers is " - "not implemented yet. %s", - ntfs_please_email); - err = -EOPNOTSUPP; - continue; - } - if (!buffer_uptodate(bh)) { - ntfs_error(vol->sb, "Writing mft mirror records " - "without existing uptodate buffers is " - "not implemented yet. %s", - ntfs_please_email); - err = -EOPNOTSUPP; - continue; - } + BUG_ON(!buffer_mapped(bh)); + BUG_ON(!buffer_uptodate(bh)); BUG_ON(!nr_bhs && (m_start != block_start)); BUG_ON(nr_bhs >= max_bhs); bhs[nr_bhs++] = bh; @@ -630,11 +530,10 @@ no_buffers_err_out: if (unlikely(!buffer_uptodate(tbh))) { err = -EIO; /* - * Set the buffer uptodate so the page & buffer - * states don't become out of sync. + * Set the buffer uptodate so the page and + * buffer states do not become out of sync. */ - if (PageUptodate(page)) - set_buffer_uptodate(tbh); + set_buffer_uptodate(tbh); } } } else /* if (unlikely(err)) */ { @@ -642,29 +541,25 @@ no_buffers_err_out: for (i_bhs = 0; i_bhs < nr_bhs; i_bhs++) clear_buffer_dirty(bhs[i_bhs]); } -unlock_err_out: /* Current state: all buffers are clean, unlocked, and uptodate. */ /* Remove the mst protection fixups again. */ post_write_mst_fixup((NTFS_RECORD*)kmirr); flush_dcache_page(page); + SetPageUptodate(page); unlock_page(page); ntfs_unmap_page(page); - if (unlikely(err)) { - /* I/O error during writing. This is really bad! */ + if (likely(!err)) { + ntfs_debug("Done."); + } else { ntfs_error(vol->sb, "I/O error while writing mft mirror " - "record 0x%lx! You should unmount the volume " - "and run chkdsk or ntfsfix.", ni->mft_no); - goto err_out; - } - ntfs_debug("Done."); - return 0; + "record 0x%lx!", mft_no); err_out: - ntfs_error(vol->sb, "Failed to synchronize $MFTMirr (error code %i). " - "Volume will be left marked dirty on umount. Run " - "ntfsfix on the partition after umounting to correct " - "this.", -err); - /* We don't want to clear the dirty bit on umount. */ - NVolSetErrors(vol); + ntfs_error(vol->sb, "Failed to synchronize $MFTMirr (error " + "code %i). Volume will be left marked dirty " + "on umount. Run ntfsfix on the partition " + "after umounting to correct this.", -err); + NVolSetErrors(vol); + } return err; } @@ -785,7 +680,7 @@ int write_mft_record_nolock(ntfs_inode *ni, MFT_RECORD *m, int sync) } /* Synchronize the mft mirror now if not @sync. */ if (!sync && ni->mft_no < vol->mftmirr_size) - sync_mft_mirror(ni, m, sync); + ntfs_sync_mft_mirror(vol, ni->mft_no, m, sync); /* Wait on i/o completion of buffers. */ for (i_bhs = 0; i_bhs < nr_bhs; i_bhs++) { struct buffer_head *tbh = bhs[i_bhs]; @@ -803,7 +698,7 @@ int write_mft_record_nolock(ntfs_inode *ni, MFT_RECORD *m, int sync) } /* If @sync, now synchronize the mft mirror. */ if (sync && ni->mft_no < vol->mftmirr_size) - sync_mft_mirror(ni, m, sync); + ntfs_sync_mft_mirror(vol, ni->mft_no, m, sync); /* Remove the mst protection fixups again. */ post_write_mst_fixup((NTFS_RECORD*)m); flush_dcache_mft_record_page(ni); @@ -839,221 +734,257 @@ err_out: } /** - * ntfs_mft_writepage - check if a metadata page contains dirty mft records - * @page: metadata page possibly containing dirty mft records - * @wbc: writeback control structure + * ntfs_may_write_mft_record - check if an mft record may be written out + * @vol: [IN] ntfs volume on which the mft record to check resides + * @mft_no: [IN] mft record number of the mft record to check + * @m: [IN] mapped mft record to check + * @locked_ni: [OUT] caller has to unlock this ntfs inode if one is returned + * + * Check if the mapped (base or extent) mft record @m with mft record number + * @mft_no belonging to the ntfs volume @vol may be written out. If necessary + * and possible the ntfs inode of the mft record is locked and the base vfs + * inode is pinned. The locked ntfs inode is then returned in @locked_ni. The + * caller is responsible for unlocking the ntfs inode and unpinning the base + * vfs inode. + * + * Return TRUE if the mft record may be written out and FALSE if not. + * + * The caller has locked the page and cleared the uptodate flag on it which + * means that we can safely write out any dirty mft records that do not have + * their inodes in icache as determined by ilookup5() as anyone + * opening/creating such an inode would block when attempting to map the mft + * record in read_cache_page() until we are finished with the write out. + * + * Here is a description of the tests we perform: * - * This is called from the VM when it wants to have a dirty $MFT/$DATA metadata - * page cache page cleaned. The VM has already locked the page and marked it - * clean. Instead of writing the page as a conventional ->writepage function - * would do, we check if the page still contains any dirty mft records (it must - * have done at some point in the past since the page was marked dirty) and if - * none are found, i.e. all mft records are clean, we unlock the page and - * return. The VM is then free to do with the page as it pleases. If on the - * other hand we do find any dirty mft records in the page, we redirty the page - * before unlocking it and returning so the VM knows that the page is still - * busy and cannot be thrown out. + * If the inode is found in icache we know the mft record must be a base mft + * record. If it is dirty, we do not write it and return FALSE as the vfs + * inode write paths will result in the access times being updated which would + * cause the base mft record to be redirtied and written out again. (We know + * the access time update will modify the base mft record because Windows + * chkdsk complains if the standard information attribute is not in the base + * mft record.) * - * Note, we do not actually write any dirty mft records here because they are - * dirty inodes and hence will be written by the VFS inode dirty code paths. - * There is no need to write them from the VM page dirty code paths, too and in - * fact once we implement journalling it would be a complete nightmare having - * two code paths leading to mft record writeout. + * If the inode is in icache and not dirty, we attempt to lock the mft record + * and if we find the lock was already taken, it is not safe to write the mft + * record and we return FALSE. + * + * If we manage to obtain the lock we have exclusive access to the mft record, + * which also allows us safe writeout of the mft record. We then set + * @locked_ni to the locked ntfs inode and return TRUE. + * + * Note we cannot just lock the mft record and sleep while waiting for the lock + * because this would deadlock due to lock reversal (normally the mft record is + * locked before the page is locked but we already have the page locked here + * when we try to lock the mft record). + * + * If the inode is not in icache we need to perform further checks. + * + * If the mft record is not a FILE record or it is a base mft record, we can + * safely write it and return TRUE. + * + * We now know the mft record is an extent mft record. We check if the inode + * corresponding to its base mft record is in icache and obtain a reference to + * it if it is. If it is not, we can safely write it and return TRUE. + * + * We now have the base inode for the extent mft record. We check if it has an + * ntfs inode for the extent mft record attached and if not it is safe to write + * the extent mft record and we return TRUE. + * + * The ntfs inode for the extent mft record is attached to the base inode so we + * attempt to lock the extent mft record and if we find the lock was already + * taken, it is not safe to write the extent mft record and we return FALSE. + * + * If we manage to obtain the lock we have exclusive access to the extent mft + * record, which also allows us safe writeout of the extent mft record. We + * set the ntfs inode of the extent mft record clean and then set @locked_ni to + * the now locked ntfs inode and return TRUE. + * + * Note, the reason for actually writing dirty mft records here and not just + * relying on the vfs inode dirty code paths is that we can have mft records + * modified without them ever having actual inodes in memory. Also we can have + * dirty mft records with clean ntfs inodes in memory. None of the described + * cases would result in the dirty mft records being written out if we only + * relied on the vfs inode dirty code paths. And these cases can really occur + * during allocation of new mft records and in particular when the + * initialized_size of the $MFT/$DATA attribute is extended and the new space + * is initialized using ntfs_mft_record_format(). The clean inode can then + * appear if the mft record is reused for a new inode before it got written + * out. */ -static int ntfs_mft_writepage(struct page *page, struct writeback_control *wbc) +BOOL ntfs_may_write_mft_record(ntfs_volume *vol, const unsigned long mft_no, + const MFT_RECORD *m, ntfs_inode **locked_ni) { - struct inode *mft_vi = page->mapping->host; - struct super_block *sb = mft_vi->i_sb; - ntfs_volume *vol = NTFS_SB(sb); - u8 *maddr; - MFT_RECORD *m; - ntfs_inode **extent_nis; - unsigned long mft_no; - int nr, i, j; - BOOL is_dirty = FALSE; + struct super_block *sb = vol->sb; + struct inode *mft_vi = vol->mft_ino; + struct inode *vi; + ntfs_inode *ni, *eni, **extent_nis; + int i; + ntfs_attr na; - BUG_ON(!PageLocked(page)); - BUG_ON(PageWriteback(page)); - BUG_ON(mft_vi != vol->mft_ino); - /* The first mft record number in the page. */ - mft_no = page->index << (PAGE_CACHE_SHIFT - vol->mft_record_size_bits); - /* Number of mft records in the page. */ - nr = PAGE_CACHE_SIZE >> vol->mft_record_size_bits; - BUG_ON(!nr); - ntfs_debug("Entering for %i inodes starting at 0x%lx.", nr, mft_no); - /* Iterate over the mft records in the page looking for a dirty one. */ - maddr = (u8*)kmap(page); + ntfs_debug("Entering for inode 0x%lx.", mft_no); /* - * Clear the page uptodate flag. This will cause anyone trying to get - * hold of the page to block on the page lock in read_cache_page(). + * Normally we do not return a locked inode so set @locked_ni to NULL. */ - BUG_ON(!PageUptodate(page)); - ClearPageUptodate(page); - for (i = 0; i < nr; ++i, ++mft_no, maddr += vol->mft_record_size) { - struct inode *vi; - ntfs_inode *ni, *eni; - ntfs_attr na; - - na.mft_no = mft_no; - na.name = NULL; - na.name_len = 0; - na.type = AT_UNUSED; - /* - * Check if the inode corresponding to this mft record is in - * the VFS inode cache and obtain a reference to it if it is. - */ - ntfs_debug("Looking for inode 0x%lx in icache.", mft_no); - /* - * For inode 0, i.e. $MFT itself, we cannot use ilookup5() from - * here or we deadlock because the inode is already locked by - * the kernel (fs/fs-writeback.c::__sync_single_inode()) and - * ilookup5() waits until the inode is unlocked before - * returning it and it never gets unlocked because - * ntfs_mft_writepage() never returns. )-: Fortunately, we - * have inode 0 pinned in icache for the duration of the mount - * so we can access it directly. - */ - if (!mft_no) { - /* Balance the below iput(). */ - vi = igrab(mft_vi); - BUG_ON(vi != mft_vi); - } else - vi = ilookup5(sb, mft_no, (test_t)ntfs_test_inode, &na); - if (vi) { - ntfs_debug("Inode 0x%lx is in icache.", mft_no); - /* The inode is in icache. Check if it is dirty. */ - ni = NTFS_I(vi); - if (!NInoDirty(ni)) { - /* The inode is not dirty, skip this record. */ - ntfs_debug("Inode 0x%lx is not dirty, " - "continuing search.", mft_no); - iput(vi); - continue; - } - ntfs_debug("Inode 0x%lx is dirty, aborting search.", + BUG_ON(!locked_ni); + *locked_ni = NULL; + /* + * Check if the inode corresponding to this mft record is in the VFS + * inode cache and obtain a reference to it if it is. + */ + ntfs_debug("Looking for inode 0x%lx in icache.", mft_no); + na.mft_no = mft_no; + na.name = NULL; + na.name_len = 0; + na.type = AT_UNUSED; + /* + * For inode 0, i.e. $MFT itself, we cannot use ilookup5() from here or + * we deadlock because the inode is already locked by the kernel + * (fs/fs-writeback.c::__sync_single_inode()) and ilookup5() waits + * until the inode is unlocked before returning it and it never gets + * unlocked because ntfs_should_write_mft_record() never returns. )-: + * Fortunately, we have inode 0 pinned in icache for the duration of + * the mount so we can access it directly. + */ + if (!mft_no) { + /* Balance the below iput(). */ + vi = igrab(mft_vi); + BUG_ON(vi != mft_vi); + } else + vi = ilookup5(sb, mft_no, (test_t)ntfs_test_inode, &na); + if (vi) { + ntfs_debug("Base inode 0x%lx is in icache.", mft_no); + /* The inode is in icache. */ + ni = NTFS_I(vi); + /* Take a reference to the ntfs inode. */ + atomic_inc(&ni->count); + /* If the inode is dirty, do not write this record. */ + if (NInoDirty(ni)) { + ntfs_debug("Inode 0x%lx is dirty, do not write it.", mft_no); - /* The inode is dirty, no need to search further. */ + atomic_dec(&ni->count); iput(vi); - is_dirty = TRUE; - break; + return FALSE; } - ntfs_debug("Inode 0x%lx is not in icache.", mft_no); - /* The inode is not in icache. */ - /* Skip the record if it is not a mft record (type "FILE"). */ - if (!ntfs_is_mft_recordp((le32*)maddr)) { - ntfs_debug("Mft record 0x%lx is not a FILE record, " - "continuing search.", mft_no); - continue; + ntfs_debug("Inode 0x%lx is not dirty.", mft_no); + /* The inode is not dirty, try to take the mft record lock. */ + if (unlikely(down_trylock(&ni->mrec_lock))) { + ntfs_debug("Mft record 0x%lx is already locked, do " + "not write it.", mft_no); + atomic_dec(&ni->count); + iput(vi); + return FALSE; } - m = (MFT_RECORD*)maddr; + ntfs_debug("Managed to lock mft record 0x%lx, write it.", + mft_no); /* - * Skip the mft record if it is not in use. FIXME: What about - * deleted/deallocated (extent) inodes? (AIA) + * The write has to occur while we hold the mft record lock so + * return the locked ntfs inode. */ - if (!(m->flags & MFT_RECORD_IN_USE)) { - ntfs_debug("Mft record 0x%lx is not in use, " - "continuing search.", mft_no); - continue; - } - /* Skip the mft record if it is a base inode. */ - if (!m->base_mft_record) { - ntfs_debug("Mft record 0x%lx is a base record, " - "continuing search.", mft_no); - continue; - } + *locked_ni = ni; + return TRUE; + } + ntfs_debug("Inode 0x%lx is not in icache.", mft_no); + /* The inode is not in icache. */ + /* Write the record if it is not a mft record (type "FILE"). */ + if (!ntfs_is_mft_record(m->magic)) { + ntfs_debug("Mft record 0x%lx is not a FILE record, write it.", + mft_no); + return TRUE; + } + /* Write the mft record if it is a base inode. */ + if (!m->base_mft_record) { + ntfs_debug("Mft record 0x%lx is a base record, write it.", + mft_no); + return TRUE; + } + /* + * This is an extent mft record. Check if the inode corresponding to + * its base mft record is in icache and obtain a reference to it if it + * is. + */ + na.mft_no = MREF_LE(m->base_mft_record); + ntfs_debug("Mft record 0x%lx is an extent record. Looking for base " + "inode 0x%lx in icache.", mft_no, na.mft_no); + vi = ilookup5(sb, na.mft_no, (test_t)ntfs_test_inode, &na); + if (!vi) { /* - * This is an extent mft record. Check if the inode - * corresponding to its base mft record is in icache. + * The base inode is not in icache, write this extent mft + * record. */ - na.mft_no = MREF_LE(m->base_mft_record); - ntfs_debug("Mft record 0x%lx is an extent record. Looking " - "for base inode 0x%lx in icache.", mft_no, - na.mft_no); - vi = ilookup5(sb, na.mft_no, (test_t)ntfs_test_inode, - &na); - if (!vi) { - /* - * The base inode is not in icache. Skip this extent - * mft record. - */ - ntfs_debug("Base inode 0x%lx is not in icache, " - "continuing search.", na.mft_no); - continue; - } - ntfs_debug("Base inode 0x%lx is in icache.", na.mft_no); + ntfs_debug("Base inode 0x%lx is not in icache, write the " + "extent record.", na.mft_no); + return TRUE; + } + ntfs_debug("Base inode 0x%lx is in icache.", na.mft_no); + /* + * The base inode is in icache. Check if it has the extent inode + * corresponding to this extent mft record attached. + */ + ni = NTFS_I(vi); + down(&ni->extent_lock); + if (ni->nr_extents <= 0) { /* - * The base inode is in icache. Check if it has the extent - * inode corresponding to this extent mft record attached. + * The base inode has no attached extent inodes, write this + * extent mft record. */ - ni = NTFS_I(vi); - down(&ni->extent_lock); - if (ni->nr_extents <= 0) { + up(&ni->extent_lock); + iput(vi); + ntfs_debug("Base inode 0x%lx has no attached extent inodes, " + "write the extent record.", na.mft_no); + return TRUE; + } + /* Iterate over the attached extent inodes. */ + extent_nis = ni->ext.extent_ntfs_inos; + for (eni = NULL, i = 0; i < ni->nr_extents; ++i) { + if (mft_no == extent_nis[i]->mft_no) { /* - * The base inode has no attached extent inodes. Skip - * this extent mft record. + * Found the extent inode corresponding to this extent + * mft record. */ - up(&ni->extent_lock); - iput(vi); - continue; - } - /* Iterate over the attached extent inodes. */ - extent_nis = ni->ext.extent_ntfs_inos; - for (eni = NULL, j = 0; j < ni->nr_extents; ++j) { - if (mft_no == extent_nis[j]->mft_no) { - /* - * Found the extent inode corresponding to this - * extent mft record. - */ - eni = extent_nis[j]; - break; - } - } - /* - * If the extent inode was not attached to the base inode, skip - * this extent mft record. - */ - if (!eni) { - up(&ni->extent_lock); - iput(vi); - continue; - } - /* - * Found the extent inode corrsponding to this extent mft - * record. If it is dirty, no need to search further. - */ - if (NInoDirty(eni)) { - up(&ni->extent_lock); - iput(vi); - is_dirty = TRUE; + eni = extent_nis[i]; break; } - /* The extent inode is not dirty, so do the next record. */ + } + /* + * If the extent inode was not attached to the base inode, write this + * extent mft record. + */ + if (!eni) { up(&ni->extent_lock); iput(vi); + ntfs_debug("Extent inode 0x%lx is not attached to its base " + "inode 0x%lx, write the extent record.", + mft_no, na.mft_no); + return TRUE; } - SetPageUptodate(page); - kunmap(page); - /* If a dirty mft record was found, redirty the page. */ - if (is_dirty) { - ntfs_debug("Inode 0x%lx is dirty. Redirtying the page " - "starting at inode 0x%lx.", mft_no, - page->index << (PAGE_CACHE_SHIFT - - vol->mft_record_size_bits)); - redirty_page_for_writepage(wbc, page); - unlock_page(page); - } else { - /* - * Keep the VM happy. This must be done otherwise the - * radix-tree tag PAGECACHE_TAG_DIRTY remains set even though - * the page is clean. - */ - BUG_ON(PageWriteback(page)); - set_page_writeback(page); - unlock_page(page); - end_page_writeback(page); + ntfs_debug("Extent inode 0x%lx is attached to its base inode 0x%lx.", + mft_no, na.mft_no); + /* Take a reference to the extent ntfs inode. */ + atomic_inc(&eni->count); + up(&ni->extent_lock); + /* + * Found the extent inode coresponding to this extent mft record. + * Try to take the mft record lock. + */ + if (unlikely(down_trylock(&eni->mrec_lock))) { + atomic_dec(&eni->count); + iput(vi); + ntfs_debug("Extent mft record 0x%lx is already locked, do " + "not write it.", mft_no); + return FALSE; } - ntfs_debug("Done."); - return 0; + ntfs_debug("Managed to lock extent mft record 0x%lx, write it.", + mft_no); + if (NInoTestClearDirty(eni)) + ntfs_debug("Extent inode 0x%lx is dirty, marking it clean.", + mft_no); + /* + * The write has to occur while we hold the mft record lock so return + * the locked extent ntfs inode. + */ + *locked_ni = eni; + return TRUE; } static const char *es = " Leaving inconsistent metadata. Unmount and run " diff --git a/fs/ntfs/mft.h b/fs/ntfs/mft.h index fddc92e21d90..c2c7a018c3f9 100644 --- a/fs/ntfs/mft.h +++ b/fs/ntfs/mft.h @@ -29,7 +29,6 @@ #include "inode.h" -extern MFT_RECORD *try_map_mft_record(ntfs_inode *ni); extern MFT_RECORD *map_mft_record(ntfs_inode *ni); extern void unmap_mft_record(ntfs_inode *ni); @@ -77,6 +76,9 @@ static inline void mark_mft_record_dirty(ntfs_inode *ni) __mark_mft_record_dirty(ni); } +extern int ntfs_sync_mft_mirror(ntfs_volume *vol, const unsigned long mft_no, + MFT_RECORD *m, int sync); + extern int write_mft_record_nolock(ntfs_inode *ni, MFT_RECORD *m, int sync); /** @@ -112,6 +114,10 @@ static inline int write_mft_record(ntfs_inode *ni, MFT_RECORD *m, int sync) return err; } +extern BOOL ntfs_may_write_mft_record(ntfs_volume *vol, + const unsigned long mft_no, const MFT_RECORD *m, + ntfs_inode **locked_ni); + extern int ntfs_extent_mft_record_free(ntfs_inode *ni, MFT_RECORD *m); #endif /* NTFS_RW */ diff --git a/fs/ntfs/ntfs.h b/fs/ntfs/ntfs.h index d65c401ccbc3..fb71b9fff857 100644 --- a/fs/ntfs/ntfs.h +++ b/fs/ntfs/ntfs.h @@ -56,7 +56,6 @@ extern kmem_cache_t *ntfs_index_ctx_cache; extern struct super_operations ntfs_sops; extern struct address_space_operations ntfs_aops; extern struct address_space_operations ntfs_mst_aops; -extern struct address_space_operations ntfs_mft_aops; extern struct file_operations ntfs_file_ops; extern struct inode_operations ntfs_file_inode_ops; diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c index 7edbdefd9d7a..d915ee20873f 100644 --- a/fs/ntfs/super.c +++ b/fs/ntfs/super.c @@ -946,8 +946,8 @@ static BOOL load_and_init_mft_mirror(ntfs_volume *vol) /* No VFS initiated operations allowed for $MFTMirr. */ tmp_ino->i_op = &ntfs_empty_inode_ops; tmp_ino->i_fop = &ntfs_empty_file_ops; - /* Put back our special address space operations. */ - tmp_ino->i_mapping->a_ops = &ntfs_mft_aops; + /* Put in our special address space operations. */ + tmp_ino->i_mapping->a_ops = &ntfs_mst_aops; tmp_ni = NTFS_I(tmp_ino); /* The $MFTMirr, like the $MFT is multi sector transfer protected. */ NInoSetMstProtected(tmp_ni); -- cgit v1.2.3 From 8f62c4026588319090229e31a34f56dee009db2d Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Thu, 14 Oct 2004 19:09:31 +0100 Subject: NTFS: - Fix two race conditions in fs/ntfs/inode.c::ntfs_put_inode(). - Fix race condition in fs/ntfs/inode.c::ntfs_put_inode() by moving the index inode bitmap inode release code from there to fs/ntfs/inode.c::ntfs_clear_big_inode(). (Thanks to Christoph Hellwig for spotting this.) - Fix race condition in fs/ntfs/inode.c::ntfs_put_inode() by taking the inode semaphore around the code thst sets ni->itype.index.bmp_ino to NULL and reorganize the code to optimize it a bit. (Thanks to Christoph Hellwig for spotting this.) Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 8 ++++++++ fs/ntfs/inode.c | 49 ++++++++++++++++++++++++------------------------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 6217c5b7c2c4..a1c774578090 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -110,6 +110,14 @@ ToDo/Notes: - Fix callers of fs/ntfs/aops.c::mark_ntfs_record_dirty() to call it with the ntfs inode which contains the page rather than the ntfs inode the mft record of which is in the page. + - Fix race condition in fs/ntfs/inode.c::ntfs_put_inode() by moving the + index inode bitmap inode release code from there to + fs/ntfs/inode.c::ntfs_clear_big_inode(). (Thanks to Christoph + Hellwig for spotting this.) + - Fix race condition in fs/ntfs/inode.c::ntfs_put_inode() by taking the + inode semaphore around the code thst sets ni->itype.index.bmp_ino to + NULL and reorganize the code to optimize it a bit. (Thanks to + Christoph Hellwig for spotting this.) 2.1.20 - Fix two stupid bugs introduced in 2.1.18 release. diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 11032d239103..3f1a44438447 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -2095,37 +2095,24 @@ err_out: * dropped, we need to put the attribute inode for the directory index bitmap, * if it is present, otherwise the directory inode would remain pinned for * ever. - * - * If the inode @vi is an index inode with only one reference which is being - * dropped, we need to put the attribute inode for the index bitmap, if it is - * present, otherwise the index inode would disappear and the attribute inode - * for the index bitmap would no longer be referenced from anywhere and thus it - * would remain pinned for ever. */ void ntfs_put_inode(struct inode *vi) { - ntfs_inode *ni; - - if (S_ISDIR(vi->i_mode)) { - if (atomic_read(&vi->i_count) == 2) { - ni = NTFS_I(vi); - if (NInoIndexAllocPresent(ni) && - ni->itype.index.bmp_ino) { - iput(ni->itype.index.bmp_ino); - ni->itype.index.bmp_ino = NULL; + if (S_ISDIR(vi->i_mode) && atomic_read(&vi->i_count) == 2) { + ntfs_inode *ni = NTFS_I(vi); + if (NInoIndexAllocPresent(ni)) { + struct inode *bvi = NULL; + down(&vi->i_sem); + if (atomic_read(&vi->i_count) == 2) { + bvi = ni->itype.index.bmp_ino; + if (bvi) + ni->itype.index.bmp_ino = NULL; } + up(&vi->i_sem); + if (bvi) + iput(bvi); } - return; - } - if (atomic_read(&vi->i_count) != 1) - return; - ni = NTFS_I(vi); - if (NInoAttr(ni) && (ni->type == AT_INDEX_ALLOCATION) && - NInoIndexAllocPresent(ni) && ni->itype.index.bmp_ino) { - iput(ni->itype.index.bmp_ino); - ni->itype.index.bmp_ino = NULL; } - return; } void __ntfs_clear_inode(ntfs_inode *ni) @@ -2193,6 +2180,18 @@ void ntfs_clear_big_inode(struct inode *vi) { ntfs_inode *ni = NTFS_I(vi); + /* + * If the inode @vi is an index inode we need to put the attribute + * inode for the index bitmap, if it is present, otherwise the index + * inode would disappear and the attribute inode for the index bitmap + * would no longer be referenced from anywhere and thus it would remain + * pinned for ever. + */ + if (NInoAttr(ni) && (ni->type == AT_INDEX_ALLOCATION) && + NInoIndexAllocPresent(ni) && ni->itype.index.bmp_ino) { + iput(ni->itype.index.bmp_ino); + ni->itype.index.bmp_ino = NULL; + } #ifdef NTFS_RW if (NInoDirty(ni)) { BOOL was_bad = (is_bad_inode(vi)); -- cgit v1.2.3 From 0cbd0fa4ac47991d38f945cb2bc51923204a0917 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Fri, 15 Oct 2004 02:12:52 -0700 Subject: USB: add endian markups to the ub driver. Signed-off-by: Greg Kroah-Hartman --- drivers/block/ub.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/block/ub.c b/drivers/block/ub.c index 0c3567159375..dd3be0a06219 100644 --- a/drivers/block/ub.c +++ b/drivers/block/ub.c @@ -63,9 +63,9 @@ /* command block wrapper */ struct bulk_cb_wrap { - u32 Signature; /* contains 'USBC' */ + __le32 Signature; /* contains 'USBC' */ u32 Tag; /* unique per command id */ - u32 DataTransferLength; /* size of data */ + __le32 DataTransferLength; /* size of data */ u8 Flags; /* direction in bit 0 */ u8 Lun; /* LUN normally 0 */ u8 Length; /* of of the CDB */ @@ -79,9 +79,9 @@ struct bulk_cb_wrap { /* command status wrapper */ struct bulk_cs_wrap { - u32 Signature; /* should = 'USBS' */ + __le32 Signature; /* should = 'USBS' */ u32 Tag; /* same as original command */ - u32 Residue; /* amount not transferred */ + __le32 Residue; /* amount not transferred */ u8 Status; /* see below */ }; @@ -1692,8 +1692,8 @@ static int ub_sync_read_cap(struct ub_dev *sc, struct ub_capacity *ret) } /* sd.c special-cases sector size of 0 to mean 512. Needed? Safe? */ - nsec = be32_to_cpu(*(u32 *)p) + 1; - bsize = be32_to_cpu(*(u32 *)(p + 4)); + nsec = be32_to_cpu(*(__be32 *)p) + 1; + bsize = be32_to_cpu(*(__be32 *)(p + 4)); switch (bsize) { case 512: shift = 0; break; case 1024: shift = 1; break; -- cgit v1.2.3 From 400f7bcb061b47c28289639a4630607db54dd422 Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Fri, 15 Oct 2004 12:43:34 +0100 Subject: NTFS: Modify fs/ntfs/aops.c::mark_ntfs_record_dirty() to no longer take the ntfs inode as a parameter as this is confusing and misleading and the ntfs inode is available via NTFS_I(page->mapping->host). Adapt all callers to this change. Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 4 ++++ fs/ntfs/aops.c | 24 +++++++++++++----------- fs/ntfs/aops.h | 3 +-- fs/ntfs/index.h | 4 ++-- fs/ntfs/inode.c | 4 ++-- fs/ntfs/mft.c | 3 +-- 6 files changed, 23 insertions(+), 19 deletions(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index a1c774578090..52670b89d938 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -118,6 +118,10 @@ ToDo/Notes: inode semaphore around the code thst sets ni->itype.index.bmp_ino to NULL and reorganize the code to optimize it a bit. (Thanks to Christoph Hellwig for spotting this.) + - Modify fs/ntfs/aops.c::mark_ntfs_record_dirty() to no longer take the + ntfs inode as a parameter as this is confusing and misleading and the + needed ntfs inode is available via NTFS_I(page->mapping->host). + Adapt all callers to this change. 2.1.20 - Fix two stupid bugs introduced in 2.1.18 release. diff --git a/fs/ntfs/aops.c b/fs/ntfs/aops.c index ede1c42b8cbf..bc1d88dd6e51 100644 --- a/fs/ntfs/aops.c +++ b/fs/ntfs/aops.c @@ -2132,9 +2132,8 @@ struct address_space_operations ntfs_mst_aops = { /** * mark_ntfs_record_dirty - mark an ntfs record dirty - * @ni: ntfs inode containing the ntfs record to be marked dirty * @page: page containing the ntfs record to mark dirty - * @rec_start: byte offset within @page at which the ntfs record begins + * @ofs: byte offset within @page at which the ntfs record begins * * If the ntfs record is the same size as the page cache page @page, set all * buffers in the page dirty. Otherwise, set only the buffers in which the @@ -2143,26 +2142,29 @@ struct address_space_operations ntfs_mst_aops = { * Also, set the page containing the ntfs record dirty, which also marks the * vfs inode the ntfs record belongs to dirty (I_DIRTY_PAGES). */ -void mark_ntfs_record_dirty(ntfs_inode *ni, struct page *page, - unsigned int rec_start) { +void mark_ntfs_record_dirty(struct page *page, const unsigned int ofs) { + ntfs_inode *ni; struct buffer_head *bh, *head; - unsigned int rec_end, bh_size, bh_start, bh_end; + unsigned int end, bh_size, bh_ofs; BUG_ON(!page); BUG_ON(!page_has_buffers(page)); + ni = NTFS_I(page->mapping->host); + BUG_ON(!ni); if (ni->itype.index.block_size == PAGE_CACHE_SIZE) { __set_page_dirty_buffers(page); return; } - rec_end = rec_start + ni->itype.index.block_size; + end = ofs + ni->itype.index.block_size; bh_size = ni->vol->sb->s_blocksize; - bh_start = 0; bh = head = page_buffers(page); do { - bh_end = bh_start + bh_size; - if ((bh_start >= rec_start) && (bh_end <= rec_end)) - set_buffer_dirty(bh); - bh_start = bh_end; + bh_ofs = bh_offset(bh); + if (bh_ofs + bh_size <= ofs) + continue; + if (unlikely(bh_ofs >= end)) + break; + set_buffer_dirty(bh); } while ((bh = bh->b_this_page) != head); __set_page_dirty_nobuffers(page); } diff --git a/fs/ntfs/aops.h b/fs/ntfs/aops.h index 2bdadc1d533b..10b23174cb5f 100644 --- a/fs/ntfs/aops.h +++ b/fs/ntfs/aops.h @@ -95,8 +95,7 @@ static inline struct page *ntfs_map_page(struct address_space *mapping, #ifdef NTFS_RW -extern void mark_ntfs_record_dirty(ntfs_inode *ni, struct page *page, - unsigned int rec_start); +extern void mark_ntfs_record_dirty(struct page *page, const unsigned int ofs); #endif /* NTFS_RW */ diff --git a/fs/ntfs/index.h b/fs/ntfs/index.h index b8f503fa2d6e..846a489e8692 100644 --- a/fs/ntfs/index.h +++ b/fs/ntfs/index.h @@ -139,8 +139,8 @@ static inline void ntfs_index_entry_mark_dirty(ntfs_index_context *ictx) if (ictx->is_in_root) mark_mft_record_dirty(ictx->actx->ntfs_ino); else - mark_ntfs_record_dirty(ictx->idx_ni, ictx->page, - (u8*)ictx->ia - (u8*)page_address(ictx->page)); + mark_ntfs_record_dirty(ictx->page, + (u8*)ictx->ia - (u8*)page_address(ictx->page)); } #endif /* NTFS_RW */ diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 3f1a44438447..f15141fbbfe6 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -2513,8 +2513,8 @@ int ntfs_write_inode(struct inode *vi, int sync) * this function returns. */ if (modified && !NInoTestSetDirty(ctx->ntfs_ino)) - mark_ntfs_record_dirty(NTFS_I(ni->vol->mft_ino), - ctx->ntfs_ino->page, ctx->ntfs_ino->page_ofs); + mark_ntfs_record_dirty(ctx->ntfs_ino->page, + ctx->ntfs_ino->page_ofs); ntfs_attr_put_search_ctx(ctx); /* Now the access times are updated, write the base mft record. */ if (NInoDirty(ni)) diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 757effcd7066..68cb2678cd50 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -380,8 +380,7 @@ void __mark_mft_record_dirty(ntfs_inode *ni) ntfs_debug("Entering for inode 0x%lx.", ni->mft_no); BUG_ON(NInoAttr(ni)); - mark_ntfs_record_dirty(NTFS_I(ni->vol->mft_ino), ni->page, - ni->page_ofs); + mark_ntfs_record_dirty(ni->page, ni->page_ofs); /* Determine the base vfs inode and mark it dirty, too. */ down(&ni->extent_lock); if (likely(ni->nr_extents >= 0)) -- cgit v1.2.3 From 921372c289951a2235c95c51aba0642b80d5fa19 Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Fri, 15 Oct 2004 12:51:22 +0100 Subject: NTFS: Modify fs/ntfs/mft.c::write_mft_record_nolock() and fs/ntfs/aops.c::ntfs_write_mst_block() to only check the dirty state of the first buffer in a record and to take this as the ntfs record dirty state. We cannot look at the dirty state for subsequent buffers because we might be racing with fs/ntfs/aops.c::mark_ntfs_record_dirty(). Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 6 ++++++ fs/ntfs/aops.c | 27 +++++++++++++-------------- fs/ntfs/mft.c | 34 +++++++++++++++++++--------------- 3 files changed, 38 insertions(+), 29 deletions(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 52670b89d938..698b00b0d9d8 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -122,6 +122,12 @@ ToDo/Notes: ntfs inode as a parameter as this is confusing and misleading and the needed ntfs inode is available via NTFS_I(page->mapping->host). Adapt all callers to this change. + - Modify fs/ntfs/mft.c::write_mft_record_nolock() and + fs/ntfs/aops.c::ntfs_write_mst_block() to only check the dirty state + of the first buffer in a record and to take this as the ntfs record + dirty state. We cannot look at the dirty state for subsequent + buffers because we might be racing with + fs/ntfs/aops.c::mark_ntfs_record_dirty(). 2.1.20 - Fix two stupid bugs introduced in 2.1.18 release. diff --git a/fs/ntfs/aops.c b/fs/ntfs/aops.c index bc1d88dd6e51..751c6d81de7a 100644 --- a/fs/ntfs/aops.c +++ b/fs/ntfs/aops.c @@ -868,23 +868,24 @@ static int ntfs_write_mst_block(struct writeback_control *wbc, clear_buffer_dirty(bh); continue; } - if (rec_block == block) { + if (likely(block < rec_block)) { + /* + * This block is not the first one in the record. We + * ignore the buffer's dirty state because we could + * have raced with a parallel mark_ntfs_record_dirty(). + */ + if (!rec_is_dirty) + continue; + } else /* if (block == rec_block) */ { + BUG_ON(block > rec_block); /* This block is the first one in the record. */ rec_block += bhs_per_rec; if (!buffer_dirty(bh)) { - /* Clean buffers are not written out. */ + /* Clean records are not written out. */ rec_is_dirty = FALSE; continue; } rec_is_dirty = TRUE; - } else { - /* This block is not the first one in the record. */ - if (!buffer_dirty(bh)) { - /* Clean buffers are not written out. */ - BUG_ON(rec_is_dirty); - continue; - } - BUG_ON(!rec_is_dirty); } BUG_ON(!buffer_mapped(bh)); BUG_ON(!buffer_uptodate(bh)); @@ -973,10 +974,8 @@ static int ntfs_write_mst_block(struct writeback_control *wbc, continue; if (unlikely(test_set_buffer_locked(tbh))) BUG(); - if (unlikely(!test_clear_buffer_dirty(tbh))) { - unlock_buffer(tbh); - continue; - } + /* The buffer dirty state is now irrelevant, just clean it. */ + clear_buffer_dirty(tbh); BUG_ON(!buffer_uptodate(tbh)); BUG_ON(!buffer_mapped(tbh)); get_bh(tbh); diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 68cb2678cd50..10c729286a82 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -572,8 +572,10 @@ err_out: * ntfs inode @ni to backing store. If the mft record @m has a counterpart in * the mft mirror, that is also updated. * - * We only write the mft record if the ntfs inode @ni is dirty and the buffers - * belonging to its mft record are dirty, too. + * We only write the mft record if the ntfs inode @ni is dirty and the first + * buffer belonging to its mft record is dirty, too. We ignore the dirty state + * of subsequent buffers because we could have raced with + * fs/ntfs/aops.c::mark_ntfs_record_dirty(). * * On success, clean the mft record and return 0. On error, leave the mft * record dirty and return -errno. The caller should call make_bad_inode() on @@ -632,21 +634,23 @@ int write_mft_record_nolock(ntfs_inode *ni, MFT_RECORD *m, int sync) continue; if (unlikely(block_start >= m_end)) break; - /* - * If the buffer is clean and it is the first buffer of the mft - * record, it was written out by other means already so we are - * done. For safety we make sure all the other buffers are - * clean also. If it is clean but not the first buffer and the - * first buffer was dirty it is a bug. - */ - if (!buffer_dirty(bh)) { - if (block_start == m_start) + if (block_start == m_start) { + /* This block is the first one in the record. */ + if (!buffer_dirty(bh)) { + /* Clean records are not written out. */ rec_is_dirty = FALSE; - else - BUG_ON(rec_is_dirty); - continue; + continue; + } + rec_is_dirty = TRUE; + } else { + /* + * This block is not the first one in the record. We + * ignore the buffer's dirty state because we could + * have raced with a parallel mark_ntfs_record_dirty(). + */ + if (!rec_is_dirty) + continue; } - BUG_ON(!rec_is_dirty); BUG_ON(!buffer_mapped(bh)); BUG_ON(!buffer_uptodate(bh)); BUG_ON(!nr_bhs && (m_start != block_start)); -- cgit v1.2.3 From 714cd1406a0a318b1c04cf47ab4b24251f7a992a Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Sat, 16 Oct 2004 01:41:53 +0100 Subject: NTFS: Move the static inline ntfs_init_big_inode() from fs/ntfs/inode.c to inode.h and make fs/ntfs/inode.c::__ntfs_init_inode() non-static and add a declaration for it to inode.h. Fix some compilation issues that resulted due to #includes and header file interdependencies. Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 4 ++++ fs/ntfs/aops.c | 2 ++ fs/ntfs/compress.c | 1 + fs/ntfs/debug.h | 6 +----- fs/ntfs/file.c | 1 + fs/ntfs/inode.c | 13 +------------ fs/ntfs/inode.h | 12 ++++++++++++ fs/ntfs/logfile.c | 5 +++-- fs/ntfs/mft.c | 8 +++++--- fs/ntfs/namei.c | 3 ++- fs/ntfs/runlist.c | 3 ++- 11 files changed, 34 insertions(+), 24 deletions(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 698b00b0d9d8..abb6cfc41344 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -128,6 +128,10 @@ ToDo/Notes: dirty state. We cannot look at the dirty state for subsequent buffers because we might be racing with fs/ntfs/aops.c::mark_ntfs_record_dirty(). + - Move the static inline ntfs_init_big_inode() from fs/ntfs/inode.c to + inode.h and make fs/ntfs/inode.c::__ntfs_init_inode() non-static and + add a declaration for it to inode.h. Fix some compilation issues + that resulted due to #includes and header file interdependencies. 2.1.20 - Fix two stupid bugs introduced in 2.1.18 release. diff --git a/fs/ntfs/aops.c b/fs/ntfs/aops.c index 751c6d81de7a..101929b277a2 100644 --- a/fs/ntfs/aops.c +++ b/fs/ntfs/aops.c @@ -29,9 +29,11 @@ #include #include "aops.h" +#include "attrib.h" #include "debug.h" #include "inode.h" #include "mft.h" +#include "runlist.h" #include "types.h" #include "ntfs.h" diff --git a/fs/ntfs/compress.c b/fs/ntfs/compress.c index b96fe5ff68dd..5d173da9b645 100644 --- a/fs/ntfs/compress.c +++ b/fs/ntfs/compress.c @@ -25,6 +25,7 @@ #include #include +#include "attrib.h" #include "inode.h" #include "debug.h" #include "ntfs.h" diff --git a/fs/ntfs/debug.h b/fs/ntfs/debug.h index 63c62602224e..8ac37c33d127 100644 --- a/fs/ntfs/debug.h +++ b/fs/ntfs/debug.h @@ -22,13 +22,9 @@ #ifndef _LINUX_NTFS_DEBUG_H #define _LINUX_NTFS_DEBUG_H -#include -#include -#include #include -#include "inode.h" -#include "attrib.h" +#include "runlist.h" #ifdef DEBUG diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c index e808649d5a8f..2ec83edb6893 100644 --- a/fs/ntfs/file.c +++ b/fs/ntfs/file.c @@ -22,6 +22,7 @@ #include #include +#include "inode.h" #include "debug.h" #include "ntfs.h" diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index f15141fbbfe6..fc4a9615c202 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -373,7 +373,7 @@ void ntfs_destroy_extent_inode(ntfs_inode *ni) * * Return zero on success and -ENOMEM on error. */ -static void __ntfs_init_inode(struct super_block *sb, ntfs_inode *ni) +void __ntfs_init_inode(struct super_block *sb, ntfs_inode *ni) { ntfs_debug("Entering."); ni->initialized_size = ni->allocated_size = 0; @@ -396,17 +396,6 @@ static void __ntfs_init_inode(struct super_block *sb, ntfs_inode *ni) init_MUTEX(&ni->extent_lock); ni->nr_extents = 0; ni->ext.base_ntfs_ino = NULL; - return; -} - -static inline void ntfs_init_big_inode(struct inode *vi) -{ - ntfs_inode *ni = NTFS_I(vi); - - ntfs_debug("Entering."); - __ntfs_init_inode(vi->i_sb, ni); - ni->mft_no = vi->i_ino; - return; } inline ntfs_inode *ntfs_new_extent_inode(struct super_block *sb, diff --git a/fs/ntfs/inode.h b/fs/ntfs/inode.h index bf1d906d1691..eb54db217e87 100644 --- a/fs/ntfs/inode.h +++ b/fs/ntfs/inode.h @@ -35,6 +35,7 @@ #include "volume.h" #include "types.h" #include "runlist.h" +#include "debug.h" typedef struct _ntfs_inode ntfs_inode; @@ -276,6 +277,17 @@ extern struct inode *ntfs_alloc_big_inode(struct super_block *sb); extern void ntfs_destroy_big_inode(struct inode *inode); extern void ntfs_clear_big_inode(struct inode *vi); +extern void __ntfs_init_inode(struct super_block *sb, ntfs_inode *ni); + +static inline void ntfs_init_big_inode(struct inode *vi) +{ + ntfs_inode *ni = NTFS_I(vi); + + ntfs_debug("Entering."); + __ntfs_init_inode(vi->i_sb, ni); + ni->mft_no = vi->i_ino; +} + extern ntfs_inode *ntfs_new_extent_inode(struct super_block *sb, unsigned long mft_no); extern void ntfs_clear_extent_inode(ntfs_inode *ni); diff --git a/fs/ntfs/logfile.c b/fs/ntfs/logfile.c index 9f20835e04a9..5e280abafab3 100644 --- a/fs/ntfs/logfile.c +++ b/fs/ntfs/logfile.c @@ -27,11 +27,12 @@ #include #include -#include "logfile.h" -#include "volume.h" +#include "attrib.h" #include "aops.h" #include "debug.h" +#include "logfile.h" #include "malloc.h" +#include "volume.h" #include "ntfs.h" /** diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 10c729286a82..ea0bbedede2f 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -23,12 +23,14 @@ #include #include -#include "bitmap.h" -#include "lcnalloc.h" +#include "attrib.h" #include "aops.h" +#include "bitmap.h" #include "debug.h" -#include "mft.h" +#include "dir.h" +#include "lcnalloc.h" #include "malloc.h" +#include "mft.h" #include "ntfs.h" /** diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c index b47084f0607b..ac5997bec5cd 100644 --- a/fs/ntfs/namei.c +++ b/fs/ntfs/namei.c @@ -23,8 +23,9 @@ #include #include -#include "dir.h" +#include "attrib.h" #include "debug.h" +#include "dir.h" #include "mft.h" #include "ntfs.h" diff --git a/fs/ntfs/runlist.c b/fs/ntfs/runlist.c index 6b9ca6883581..50bfb6b5b3e0 100644 --- a/fs/ntfs/runlist.c +++ b/fs/ntfs/runlist.c @@ -20,8 +20,9 @@ * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "dir.h" #include "debug.h" +#include "dir.h" +#include "endian.h" #include "malloc.h" #include "ntfs.h" -- cgit v1.2.3 From fa18d04a100a44852744bc2a4c8ed9aefe93d55f Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Sat, 16 Oct 2004 01:47:49 +0100 Subject: NTFS: Simplify setup of i_mode in fs/ntfs/inode.c::ntfs_read_locked_inode(). Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 1 + fs/ntfs/inode.c | 33 ++++++++++++++------------------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index abb6cfc41344..92f80a1403d7 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -132,6 +132,7 @@ ToDo/Notes: inode.h and make fs/ntfs/inode.c::__ntfs_init_inode() non-static and add a declaration for it to inode.h. Fix some compilation issues that resulted due to #includes and header file interdependencies. + - Simplify setup of i_mode in fs/ntfs/inode.c::ntfs_read_locked_inode(). 2.1.20 - Fix two stupid bugs introduced in 2.1.18 release. diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index fc4a9615c202..181f9b8df900 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -594,14 +594,26 @@ static int ntfs_read_locked_inode(struct inode *vi) * Also if not a directory, it could be something else, rather than * a regular file. But again, will do for now. */ + /* Everyone gets all permissions. */ + vi->i_mode |= S_IRWXUGO; + /* If read-only, noone gets write permissions. */ + if (IS_RDONLY(vi)) + vi->i_mode &= ~S_IWUGO; if (m->flags & MFT_RECORD_IS_DIRECTORY) { vi->i_mode |= S_IFDIR; + /* + * Apply the directory permissions mask set in the mount + * options. + */ + vi->i_mode &= ~vol->dmask; /* Things break without this kludge! */ if (vi->i_nlink > 1) vi->i_nlink = 1; - } else + } else { vi->i_mode |= S_IFREG; - + /* Apply the file permissions mask set in the mount options. */ + vi->i_mode &= ~vol->fmask; + } /* * Find the standard information attribute in the mft record. At this * stage we haven't setup the attribute list stuff yet, so this could @@ -944,16 +956,6 @@ skip_attr_list_load: goto unm_err_out; } skip_large_dir_stuff: - /* Everyone gets read and scan permissions. */ - vi->i_mode |= S_IRUGO | S_IXUGO; - /* If not read-only, set write permissions. */ - if (!IS_RDONLY(vi)) - vi->i_mode |= S_IWUGO; - /* - * Apply the directory permissions mask set in the mount - * options. - */ - vi->i_mode &= ~vol->dmask; /* Setup the operations for this inode. */ vi->i_op = &ntfs_dir_inode_ops; vi->i_fop = &ntfs_dir_ops; @@ -1090,13 +1092,6 @@ no_data_attr_special_case: unmap_mft_record(ni); m = NULL; ctx = NULL; - /* Everyone gets all permissions. */ - vi->i_mode |= S_IRWXUGO; - /* If read-only, noone gets write permissions. */ - if (IS_RDONLY(vi)) - vi->i_mode &= ~S_IWUGO; - /* Apply the file permissions mask set in the mount options. */ - vi->i_mode &= ~vol->fmask; /* Setup the operations for this inode. */ vi->i_op = &ntfs_file_inode_ops; vi->i_fop = &ntfs_file_ops; -- cgit v1.2.3 From dbe84f1e22d3489c4bf3bc0ba0c8ab77f2f2fe42 Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Sat, 16 Oct 2004 01:51:00 +0100 Subject: NTFS: Add helpers fs/ntfs/layout.h::MK_MREF() and MK_LE_MREF(). Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 1 + fs/ntfs/layout.h | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 92f80a1403d7..e555cd1a3449 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -133,6 +133,7 @@ ToDo/Notes: add a declaration for it to inode.h. Fix some compilation issues that resulted due to #includes and header file interdependencies. - Simplify setup of i_mode in fs/ntfs/inode.c::ntfs_read_locked_inode(). + - Add helpers fs/ntfs/layout.h::MK_MREF() and MK_LE_MREF(). 2.1.20 - Fix two stupid bugs introduced in 2.1.18 release. diff --git a/fs/ntfs/layout.h b/fs/ntfs/layout.h index 94d3440d260d..a34eed5b922d 100644 --- a/fs/ntfs/layout.h +++ b/fs/ntfs/layout.h @@ -316,6 +316,10 @@ typedef enum { typedef u64 MFT_REF; typedef le64 leMFT_REF; +#define MK_MREF(m, s) ((MFT_REF)(((MFT_REF)(s) << 48) | \ + ((MFT_REF)(m) & MFT_REF_MASK_CPU))) +#define MK_LE_MREF(m, s) cpu_to_le64(MK_MREF(m, s)) + #define MREF(x) ((unsigned long)((x) & MFT_REF_MASK_CPU)) #define MSEQNO(x) ((u16)(((x) >> 48) & 0xffff)) #define MREF_LE(x) ((unsigned long)(le64_to_cpu(x) & MFT_REF_MASK_CPU)) -- cgit v1.2.3 From 9d401e5bfd7bc760131ecb94819fbce218a0ed5f Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Sat, 16 Oct 2004 01:55:57 +0100 Subject: NTFS: Modify fs/ntfs/mft.c::map_extent_mft_record() to only verify the mft record sequence number if it is specified (i.e. not zero). Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 2 ++ fs/ntfs/mft.c | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index e555cd1a3449..13da1337fd85 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -134,6 +134,8 @@ ToDo/Notes: that resulted due to #includes and header file interdependencies. - Simplify setup of i_mode in fs/ntfs/inode.c::ntfs_read_locked_inode(). - Add helpers fs/ntfs/layout.h::MK_MREF() and MK_LE_MREF(). + - Modify fs/ntfs/mft.c::map_extent_mft_record() to only verify the mft + record sequence number if it is specified (i.e. not zero). 2.1.20 - Fix two stupid bugs introduced in 2.1.18 release. diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index ea0bbedede2f..e3ff64fd8522 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -302,8 +302,8 @@ map_err_out: ntfs_clear_extent_inode(ni); goto map_err_out; } - /* Verify the sequence number. */ - if (unlikely(le16_to_cpu(m->sequence_number) != seq_no)) { + /* Verify the sequence number if it is present. */ + if (seq_no && (le16_to_cpu(m->sequence_number) != seq_no)) { ntfs_error(base_ni->vol->sb, "Found stale extent mft " "reference! Corrupt file system. Run chkdsk."); destroy_ni = TRUE; -- cgit v1.2.3 From b16d21bd28db27ad9f29a31bf47b76505585de58 Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Sat, 16 Oct 2004 02:01:40 +0100 Subject: NTFS: Add fs/ntfs/mft.[hc]::ntfs_mft_record_alloc() and various helper functions used by it. Signed-off-by: Anton Altaparmakov --- fs/ntfs/ChangeLog | 2 + fs/ntfs/mft.c | 1573 +++++++++++++++++++++++++++++++++++++++++++++++++++++ fs/ntfs/mft.h | 2 + 3 files changed, 1577 insertions(+) diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 13da1337fd85..218b4c8bb947 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -136,6 +136,8 @@ ToDo/Notes: - Add helpers fs/ntfs/layout.h::MK_MREF() and MK_LE_MREF(). - Modify fs/ntfs/mft.c::map_extent_mft_record() to only verify the mft record sequence number if it is specified (i.e. not zero). + - Add fs/ntfs/mft.[hc]::ntfs_mft_record_alloc() and various helper + functions used by it. 2.1.20 - Fix two stupid bugs introduced in 2.1.18 release. diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index e3ff64fd8522..982cf8e37ba6 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -995,6 +995,1579 @@ BOOL ntfs_may_write_mft_record(ntfs_volume *vol, const unsigned long mft_no, static const char *es = " Leaving inconsistent metadata. Unmount and run " "chkdsk."; +/** + * ntfs_mft_bitmap_find_and_alloc_free_rec_nolock - see name + * @vol: volume on which to search for a free mft record + * @base_ni: open base inode if allocating an extent mft record or NULL + * + * Search for a free mft record in the mft bitmap attribute on the ntfs volume + * @vol. + * + * If @base_ni is NULL start the search at the default allocator position. + * + * If @base_ni is not NULL start the search at the mft record after the base + * mft record @base_ni. + * + * Return the free mft record on success and -errno on error. An error code of + * -ENOSPC means that there are no free mft records in the currently + * initialized mft bitmap. + * + * Locking: Caller must hold vol->mftbmp_lock for writing. + */ +static int ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(ntfs_volume *vol, + ntfs_inode *base_ni) +{ + s64 pass_end, ll, data_pos, pass_start, ofs, bit; + struct address_space *mftbmp_mapping; + u8 *buf, *byte; + struct page *page; + unsigned int page_ofs, size; + u8 pass, b; + + ntfs_debug("Searching for free mft record in the currently " + "initialized mft bitmap."); + mftbmp_mapping = vol->mftbmp_ino->i_mapping; + /* + * Set the end of the pass making sure we do not overflow the mft + * bitmap. + */ + pass_end = NTFS_I(vol->mft_ino)->allocated_size >> + vol->mft_record_size_bits; + ll = NTFS_I(vol->mftbmp_ino)->initialized_size << 3; + if (pass_end > ll) + pass_end = ll; + pass = 1; + if (!base_ni) + data_pos = vol->mft_data_pos; + else + data_pos = base_ni->mft_no + 1; + if (data_pos < 24) + data_pos = 24; + if (data_pos >= pass_end) { + data_pos = 24; + pass = 2; + /* This happens on a freshly formatted volume. */ + if (data_pos >= pass_end) + return -ENOSPC; + } + pass_start = data_pos; + ntfs_debug("Starting bitmap search: pass %u, pass_start 0x%llx, " + "pass_end 0x%llx, data_pos 0x%llx.", pass, + (long long)pass_start, (long long)pass_end, + (long long)data_pos); + /* Loop until a free mft record is found. */ + for (; pass <= 2;) { + /* Cap size to pass_end. */ + ofs = data_pos >> 3; + page_ofs = ofs & ~PAGE_CACHE_MASK; + size = PAGE_CACHE_SIZE - page_ofs; + ll = ((pass_end + 7) >> 3) - ofs; + if (size > ll) + size = ll; + size <<= 3; + /* + * If we are still within the active pass, search the next page + * for a zero bit. + */ + if (size) { + page = ntfs_map_page(mftbmp_mapping, + ofs >> PAGE_CACHE_SHIFT); + if (unlikely(IS_ERR(page))) { + ntfs_error(vol->sb, "Failed to read mft " + "bitmap, aborting."); + return PTR_ERR(page); + } + buf = (u8*)page_address(page) + page_ofs; + bit = data_pos & 7; + data_pos &= ~7ull; + ntfs_debug("Before inner for loop: size 0x%x, " + "data_pos 0x%llx, bit 0x%llx", size, + (long long)data_pos, (long long)bit); + for (; bit < size && data_pos + bit < pass_end; + bit &= ~7ull, bit += 8) { + byte = buf + (bit >> 3); + if (*byte == 0xff) + continue; + b = ffz((unsigned long)*byte); + if (b < 8 && b >= (bit & 7)) { + ll = data_pos + (bit & ~7ull) + b; + if (unlikely(ll > (1ll << 32))) { + ntfs_unmap_page(page); + return -ENOSPC; + } + *byte |= 1 << b; + flush_dcache_page(page); + set_page_dirty(page); + ntfs_unmap_page(page); + ntfs_debug("Done. (Found and " + "allocated mft record " + "0x%llx.)", + (long long)ll); + return ll; + } + } + ntfs_debug("After inner for loop: size 0x%x, " + "data_pos 0x%llx, bit 0x%llx", size, + (long long)data_pos, (long long)bit); + data_pos += size; + ntfs_unmap_page(page); + /* + * If the end of the pass has not been reached yet, + * continue searching the mft bitmap for a zero bit. + */ + if (data_pos < pass_end) + continue; + } + /* Do the next pass. */ + if (++pass == 2) { + /* + * Starting the second pass, in which we scan the first + * part of the zone which we omitted earlier. + */ + pass_end = pass_start; + data_pos = pass_start = 24; + ntfs_debug("pass %i, pass_start 0x%llx, pass_end " + "0x%llx.", pass, (long long)pass_start, + (long long)pass_end); + if (data_pos >= pass_end) + break; + } + } + /* No free mft records in currently initialized mft bitmap. */ + ntfs_debug("Done. (No free mft records left in currently initialized " + "mft bitmap.)"); + return -ENOSPC; +} + +/** + * ntfs_mft_bitmap_extend_allocation_nolock - extend mft bitmap by a cluster + * @vol: volume on which to extend the mft bitmap attribute + * + * Extend the mft bitmap attribute on the ntfs volume @vol by one cluster. + * + * Note: Only changes allocated_size, i.e. does not touch initialized_size or + * data_size. + * + * Return 0 on success and -errno on error. + * + * Locking: - Caller must hold vol->mftbmp_lock for writing. + * - This function takes NTFS_I(vol->mftbmp_ino)->runlist.lock for + * writing and releases it before returning. + * - This function takes vol->lcnbmp_lock for writing and releases it + * before returning. + */ +static int ntfs_mft_bitmap_extend_allocation_nolock(ntfs_volume *vol) +{ + LCN lcn; + s64 ll; + struct page *page; + ntfs_inode *mft_ni, *mftbmp_ni; + runlist_element *rl, *rl2 = NULL; + ntfs_attr_search_ctx *ctx = NULL; + MFT_RECORD *mrec; + ATTR_RECORD *a = NULL; + int ret, mp_size; + u32 old_alen = 0; + u8 *b, tb; + struct { + u8 added_cluster:1; + u8 added_run:1; + u8 mp_rebuilt:1; + } status = { 0, 0, 0 }; + + ntfs_debug("Extending mft bitmap allocation."); + mft_ni = NTFS_I(vol->mft_ino); + mftbmp_ni = NTFS_I(vol->mftbmp_ino); + /* + * Determine the last lcn of the mft bitmap. The allocated size of the + * mft bitmap cannot be zero so we are ok to do this. + * ntfs_find_vcn() returns the runlist locked on success. + */ + rl = ntfs_find_vcn(mftbmp_ni, (mftbmp_ni->allocated_size - 1) >> + vol->cluster_size_bits, TRUE); + if (unlikely(IS_ERR(rl) || !rl->length || rl->lcn < 0)) { + ntfs_error(vol->sb, "Failed to determine last allocated " + "cluster of mft bitmap attribute."); + if (!IS_ERR(rl)) { + up_write(&mftbmp_ni->runlist.lock); + ret = -EIO; + } else + ret = PTR_ERR(rl); + return ret; + } + lcn = rl->lcn + rl->length; + ntfs_debug("Last lcn of mft bitmap attribute is 0x%llx.", + (long long)lcn); + /* + * Attempt to get the cluster following the last allocated cluster by + * hand as it may be in the MFT zone so the allocator would not give it + * to us. + */ + ll = lcn >> 3; + page = ntfs_map_page(vol->lcnbmp_ino->i_mapping, + ll >> PAGE_CACHE_SHIFT); + if (IS_ERR(page)) { + up_write(&mftbmp_ni->runlist.lock); + ntfs_error(vol->sb, "Failed to read from lcn bitmap."); + return PTR_ERR(page); + } + b = (u8*)page_address(page) + (ll & ~PAGE_CACHE_MASK); + tb = 1 << (lcn & 7ull); + down_write(&vol->lcnbmp_lock); + if (*b != 0xff && !(*b & tb)) { + /* Next cluster is free, allocate it. */ + *b |= tb; + flush_dcache_page(page); + set_page_dirty(page); + up_write(&vol->lcnbmp_lock); + ntfs_unmap_page(page); + /* Update the mft bitmap runlist. */ + rl->length++; + rl[1].vcn++; + status.added_cluster = 1; + ntfs_debug("Appending one cluster to mft bitmap."); + } else { + up_write(&vol->lcnbmp_lock); + ntfs_unmap_page(page); + /* Allocate a cluster from the DATA_ZONE. */ + rl2 = ntfs_cluster_alloc(vol, rl[1].vcn, 1, lcn, DATA_ZONE); + if (IS_ERR(rl2)) { + up_write(&mftbmp_ni->runlist.lock); + ntfs_error(vol->sb, "Failed to allocate a cluster for " + "the mft bitmap."); + return PTR_ERR(rl2); + } + rl = ntfs_runlists_merge(mftbmp_ni->runlist.rl, rl2); + if (IS_ERR(rl)) { + up_write(&mftbmp_ni->runlist.lock); + ntfs_error(vol->sb, "Failed to merge runlists for mft " + "bitmap."); + if (ntfs_cluster_free_from_rl(vol, rl2)) { + ntfs_error(vol->sb, "Failed to dealocate " + "allocated cluster.%s", es); + NVolSetErrors(vol); + } + ntfs_free(rl2); + return PTR_ERR(rl); + } + mftbmp_ni->runlist.rl = rl; + status.added_run = 1; + ntfs_debug("Adding one run to mft bitmap."); + /* Find the last run in the new runlist. */ + for (; rl[1].length; rl++) + ; + } + /* + * Update the attribute record as well. Note: @rl is the last + * (non-terminator) runlist element of mft bitmap. + */ + mrec = map_mft_record(mft_ni); + if (IS_ERR(mrec)) { + ntfs_error(vol->sb, "Failed to map mft record."); + ret = PTR_ERR(mrec); + goto undo_alloc; + } + ctx = ntfs_attr_get_search_ctx(mft_ni, mrec); + if (unlikely(!ctx)) { + ntfs_error(vol->sb, "Failed to get search context."); + ret = -ENOMEM; + goto undo_alloc; + } + ret = ntfs_attr_lookup(mftbmp_ni->type, mftbmp_ni->name, + mftbmp_ni->name_len, CASE_SENSITIVE, rl[1].vcn, NULL, + 0, ctx); + if (unlikely(ret)) { + ntfs_error(vol->sb, "Failed to find last attribute extent of " + "mft bitmap attribute."); + if (ret == -ENOENT) + ret = -EIO; + goto undo_alloc; + } + a = ctx->attr; + ll = sle64_to_cpu(a->data.non_resident.lowest_vcn); + /* Search back for the previous last allocated cluster of mft bitmap. */ + for (rl2 = rl; rl2 > mftbmp_ni->runlist.rl; rl2--) { + if (ll >= rl2->vcn) + break; + } + BUG_ON(ll < rl2->vcn); + BUG_ON(ll >= rl2->vcn + rl2->length); + /* Get the size for the new mapping pairs array for this extent. */ + mp_size = ntfs_get_size_for_mapping_pairs(vol, rl2, ll); + if (unlikely(mp_size <= 0)) { + ntfs_error(vol->sb, "Get size for mapping pairs failed for " + "mft bitmap attribute extent."); + ret = mp_size; + if (!ret) + ret = -EIO; + goto undo_alloc; + } + /* Expand the attribute record if necessary. */ + old_alen = le32_to_cpu(a->length); + ret = ntfs_attr_record_resize(ctx->mrec, a, mp_size + + le16_to_cpu(a->data.non_resident.mapping_pairs_offset)); + if (unlikely(ret)) { + if (ret != -ENOSPC) { + ntfs_error(vol->sb, "Failed to resize attribute " + "record for mft bitmap attribute."); + goto undo_alloc; + } + // TODO: Deal with this by moving this extent to a new mft + // record or by starting a new extent in a new mft record or by + // moving other attributes out of this mft record. + ntfs_error(vol->sb, "Not enough space in this mft record to " + "accomodate extended mft bitmap attribute " + "extent. Cannot handle this yet."); + ret = -EOPNOTSUPP; + goto undo_alloc; + } + status.mp_rebuilt = 1; + /* Generate the mapping pairs array directly into the attr record. */ + ret = ntfs_mapping_pairs_build(vol, (u8*)a + + le16_to_cpu(a->data.non_resident.mapping_pairs_offset), + mp_size, rl2, ll, NULL); + if (unlikely(ret)) { + ntfs_error(vol->sb, "Failed to build mapping pairs array for " + "mft bitmap attribute."); + goto undo_alloc; + } + /* Update the highest_vcn. */ + a->data.non_resident.highest_vcn = cpu_to_sle64(rl[1].vcn - 1); + /* + * We now have extended the mft bitmap allocated_size by one cluster. + * Reflect this in the ntfs_inode structure and the attribute record. + */ + if (a->data.non_resident.lowest_vcn) { + /* + * We are not in the first attribute extent, switch to it, but + * first ensure the changes will make it to disk later. + */ + flush_dcache_mft_record_page(ctx->ntfs_ino); + mark_mft_record_dirty(ctx->ntfs_ino); + ntfs_attr_reinit_search_ctx(ctx); + ret = ntfs_attr_lookup(mftbmp_ni->type, mftbmp_ni->name, + mftbmp_ni->name_len, CASE_SENSITIVE, 0, NULL, + 0, ctx); + if (unlikely(ret)) { + ntfs_error(vol->sb, "Failed to find first attribute " + "extent of mft bitmap attribute."); + goto restore_undo_alloc; + } + a = ctx->attr; + } + mftbmp_ni->allocated_size += vol->cluster_size; + a->data.non_resident.allocated_size = + cpu_to_sle64(mftbmp_ni->allocated_size); + /* Ensure the changes make it to disk. */ + flush_dcache_mft_record_page(ctx->ntfs_ino); + mark_mft_record_dirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(mft_ni); + up_write(&mftbmp_ni->runlist.lock); + ntfs_debug("Done."); + return 0; +restore_undo_alloc: + ntfs_attr_reinit_search_ctx(ctx); + if (ntfs_attr_lookup(mftbmp_ni->type, mftbmp_ni->name, + mftbmp_ni->name_len, CASE_SENSITIVE, rl[1].vcn, NULL, + 0, ctx)) { + ntfs_error(vol->sb, "Failed to find last attribute extent of " + "mft bitmap attribute.%s", es); + mftbmp_ni->allocated_size += vol->cluster_size; + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(mft_ni); + up_write(&mftbmp_ni->runlist.lock); + /* + * The only thing that is now wrong is ->allocated_size of the + * base attribute extent which chkdsk should be able to fix. + */ + NVolSetErrors(vol); + return ret; + } + a = ctx->attr; + a->data.non_resident.highest_vcn = cpu_to_sle64(rl[1].vcn - 2); +undo_alloc: + if (status.added_cluster) { + /* Truncate the last run in the runlist by one cluster. */ + rl->length--; + rl[1].vcn--; + } else if (status.added_run) { + lcn = rl->lcn; + /* Remove the last run from the runlist. */ + rl->lcn = rl[1].lcn; + rl->length = 0; + } + /* Deallocate the cluster. */ + down_write(&vol->lcnbmp_lock); + if (ntfs_bitmap_clear_bit(vol->lcnbmp_ino, lcn)) { + ntfs_error(vol->sb, "Failed to free allocated cluster.%s", es); + NVolSetErrors(vol); + } + up_write(&vol->lcnbmp_lock); + if (status.mp_rebuilt) { + if (ntfs_mapping_pairs_build(vol, (u8*)a + le16_to_cpu( + a->data.non_resident.mapping_pairs_offset), + old_alen - le16_to_cpu( + a->data.non_resident.mapping_pairs_offset), + rl2, ll, NULL)) { + ntfs_error(vol->sb, "Failed to restore mapping pairs " + "array.%s", es); + NVolSetErrors(vol); + } + if (ntfs_attr_record_resize(ctx->mrec, a, old_alen)) { + ntfs_error(vol->sb, "Failed to restore attribute " + "record.%s", es); + NVolSetErrors(vol); + } + flush_dcache_mft_record_page(ctx->ntfs_ino); + mark_mft_record_dirty(ctx->ntfs_ino); + } + if (ctx) + ntfs_attr_put_search_ctx(ctx); + if (!IS_ERR(mrec)) + unmap_mft_record(mft_ni); + up_write(&mftbmp_ni->runlist.lock); + return ret; +} + +/** + * ntfs_mft_bitmap_extend_initialized_nolock - extend mftbmp initialized data + * @vol: volume on which to extend the mft bitmap attribute + * + * Extend the initialized portion of the mft bitmap attribute on the ntfs + * volume @vol by 8 bytes. + * + * Note: Only changes initialized_size and data_size, i.e. requires that + * allocated_size is big enough to fit the new initialized_size. + * + * Return 0 on success and -error on error. + * + * Locking: Caller must hold vol->mftbmp_lock for writing. + */ +static int ntfs_mft_bitmap_extend_initialized_nolock(ntfs_volume *vol) +{ + s64 old_data_size, old_initialized_size; + struct inode *mftbmp_vi; + ntfs_inode *mft_ni, *mftbmp_ni; + ntfs_attr_search_ctx *ctx; + MFT_RECORD *mrec; + ATTR_RECORD *a; + int ret; + + ntfs_debug("Extending mft bitmap initiailized (and data) size."); + mft_ni = NTFS_I(vol->mft_ino); + mftbmp_vi = vol->mftbmp_ino; + mftbmp_ni = NTFS_I(mftbmp_vi); + /* Get the attribute record. */ + mrec = map_mft_record(mft_ni); + if (IS_ERR(mrec)) { + ntfs_error(vol->sb, "Failed to map mft record."); + return PTR_ERR(mrec); + } + ctx = ntfs_attr_get_search_ctx(mft_ni, mrec); + if (unlikely(!ctx)) { + ntfs_error(vol->sb, "Failed to get search context."); + ret = -ENOMEM; + goto unm_err_out; + } + ret = ntfs_attr_lookup(mftbmp_ni->type, mftbmp_ni->name, + mftbmp_ni->name_len, CASE_SENSITIVE, 0, NULL, 0, ctx); + if (unlikely(ret)) { + ntfs_error(vol->sb, "Failed to find first attribute extent of " + "mft bitmap attribute."); + if (ret == -ENOENT) + ret = -EIO; + goto put_err_out; + } + a = ctx->attr; + old_data_size = mftbmp_vi->i_size; + old_initialized_size = mftbmp_ni->initialized_size; + /* + * We can simply update the initialized_size before filling the space + * with zeroes because the caller is holding the mft bitmap lock for + * writing which ensures that no one else is trying to access the data. + */ + mftbmp_ni->initialized_size += 8; + a->data.non_resident.initialized_size = + cpu_to_sle64(mftbmp_ni->initialized_size); + if (mftbmp_ni->initialized_size > mftbmp_vi->i_size) { + mftbmp_vi->i_size = mftbmp_ni->initialized_size; + a->data.non_resident.data_size = + cpu_to_sle64(mftbmp_vi->i_size); + } + /* Ensure the changes make it to disk. */ + flush_dcache_mft_record_page(ctx->ntfs_ino); + mark_mft_record_dirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(mft_ni); + /* Initialize the mft bitmap attribute value with zeroes. */ + ret = ntfs_attr_set(mftbmp_ni, old_initialized_size, 8, 0); + if (likely(!ret)) { + ntfs_debug("Done. (Wrote eight initialized bytes to mft " + "bitmap."); + return 0; + } + ntfs_error(vol->sb, "Failed to write to mft bitmap."); + /* Try to recover from the error. */ + mrec = map_mft_record(mft_ni); + if (IS_ERR(mrec)) { + ntfs_error(vol->sb, "Failed to map mft record.%s", es); + NVolSetErrors(vol); + return ret; + } + ctx = ntfs_attr_get_search_ctx(mft_ni, mrec); + if (unlikely(!ctx)) { + ntfs_error(vol->sb, "Failed to get search context.%s", es); + NVolSetErrors(vol); + goto unm_err_out; + } + if (ntfs_attr_lookup(mftbmp_ni->type, mftbmp_ni->name, + mftbmp_ni->name_len, CASE_SENSITIVE, 0, NULL, 0, ctx)) { + ntfs_error(vol->sb, "Failed to find first attribute extent of " + "mft bitmap attribute.%s", es); + NVolSetErrors(vol); +put_err_out: + ntfs_attr_put_search_ctx(ctx); +unm_err_out: + unmap_mft_record(mft_ni); + goto err_out; + } + a = ctx->attr; + mftbmp_ni->initialized_size = old_initialized_size; + a->data.non_resident.initialized_size = + cpu_to_sle64(old_initialized_size); + if (mftbmp_vi->i_size != old_data_size) { + mftbmp_vi->i_size = old_data_size; + a->data.non_resident.data_size = cpu_to_sle64(old_data_size); + } + flush_dcache_mft_record_page(ctx->ntfs_ino); + mark_mft_record_dirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(mft_ni); + ntfs_debug("Restored status of mftbmp: allocated_size 0x%llx, " + "data_size 0x%llx, initialized_size 0x%llx.", + (long long)mftbmp_ni->allocated_size, + (long long)mftbmp_vi->i_size, + (long long)mftbmp_ni->initialized_size); +err_out: + return ret; +} + +/** + * ntfs_mft_data_extend_allocation_nolock - extend mft data attribute + * @vol: volume on which to extend the mft data attribute + * + * Extend the mft data attribute on the ntfs volume @vol by 16 mft records + * worth of clusters or if not enough space for this by one mft record worth + * of clusters. + * + * Note: Only changes allocated_size, i.e. does not touch initialized_size or + * data_size. + * + * Return 0 on success and -errno on error. + * + * Locking: - Caller must hold vol->mftbmp_lock for writing. + * - This function takes NTFS_I(vol->mft_ino)->runlist.lock for + * writing and releases it before returning. + * - This function calls functions which take vol->lcnbmp_lock for + * writing and release it before returning. + */ +static int ntfs_mft_data_extend_allocation_nolock(ntfs_volume *vol) +{ + LCN lcn; + VCN old_last_vcn; + s64 min_nr, nr, ll = 0; + ntfs_inode *mft_ni; + runlist_element *rl, *rl2; + ntfs_attr_search_ctx *ctx = NULL; + MFT_RECORD *mrec; + ATTR_RECORD *a = NULL; + int ret, mp_size; + u32 old_alen = 0; + BOOL mp_rebuilt = FALSE; + + ntfs_debug("Extending mft data allocation."); + mft_ni = NTFS_I(vol->mft_ino); + /* + * Determine the preferred allocation location, i.e. the last lcn of + * the mft data attribute. The allocated size of the mft data + * attribute cannot be zero so we are ok to do this. + * ntfs_find_vcn() returns the runlist locked on success. + */ + rl = ntfs_find_vcn(mft_ni, (mft_ni->allocated_size - 1) >> + vol->cluster_size_bits, TRUE); + if (unlikely(IS_ERR(rl) || !rl->length || rl->lcn < 0)) { + ntfs_error(vol->sb, "Failed to determine last allocated " + "cluster of mft data attribute."); + if (!IS_ERR(rl)) { + up_write(&mft_ni->runlist.lock); + ret = -EIO; + } else + ret = PTR_ERR(rl); + return ret; + } + lcn = rl->lcn + rl->length; + ntfs_debug("Last lcn of mft data attribute is 0x%llx.", + (long long)lcn); + /* Minimum allocation is one mft record worth of clusters. */ + min_nr = vol->mft_record_size >> vol->cluster_size_bits; + if (!min_nr) + min_nr = 1; + /* Want to allocate 16 mft records worth of clusters. */ + nr = vol->mft_record_size << 4 >> vol->cluster_size_bits; + if (!nr) + nr = min_nr; + /* Ensure we do not go above 2^32-1 mft records. */ + if (unlikely((mft_ni->allocated_size + + (nr << vol->cluster_size_bits)) >> + vol->mft_record_size_bits >= (1ll << 32))) { + nr = min_nr; + if (unlikely((mft_ni->allocated_size + + (nr << vol->cluster_size_bits)) >> + vol->mft_record_size_bits >= (1ll << 32))) { + ntfs_warning(vol->sb, "Cannot allocate mft record " + "because the maximum number of inodes " + "(2^32) has already been reached."); + up_write(&mft_ni->runlist.lock); + return -ENOSPC; + } + } + ntfs_debug("Trying mft data allocation with %s cluster count %lli.", + nr > min_nr ? "default" : "minimal", (long long)nr); + old_last_vcn = rl[1].vcn; + do { + rl2 = ntfs_cluster_alloc(vol, old_last_vcn, nr, lcn, MFT_ZONE); + if (likely(!IS_ERR(rl2))) + break; + if (PTR_ERR(rl2) != -ENOSPC || nr == min_nr) { + ntfs_error(vol->sb, "Failed to allocate the minimal " + "number of clusters (%lli) for the " + "mft data attribute.", (long long)nr); + up_write(&mft_ni->runlist.lock); + return PTR_ERR(rl2); + } + /* + * There is not enough space to do the allocation, but there + * might be enough space to do a minimal allocation so try that + * before failing. + */ + nr = min_nr; + ntfs_debug("Retrying mft data allocation with minimal cluster " + "count %lli.", (long long)nr); + } while (1); + rl = ntfs_runlists_merge(mft_ni->runlist.rl, rl2); + if (IS_ERR(rl)) { + up_write(&mft_ni->runlist.lock); + ntfs_error(vol->sb, "Failed to merge runlists for mft data " + "attribute."); + if (ntfs_cluster_free_from_rl(vol, rl2)) { + ntfs_error(vol->sb, "Failed to dealocate clusters " + "from the mft data attribute.%s", es); + NVolSetErrors(vol); + } + ntfs_free(rl2); + return PTR_ERR(rl); + } + mft_ni->runlist.rl = rl; + ntfs_debug("Allocated %lli clusters.", nr); + /* Find the last run in the new runlist. */ + for (; rl[1].length; rl++) + ; + /* Update the attribute record as well. */ + mrec = map_mft_record(mft_ni); + if (IS_ERR(mrec)) { + ntfs_error(vol->sb, "Failed to map mft record."); + ret = PTR_ERR(mrec); + goto undo_alloc; + } + ctx = ntfs_attr_get_search_ctx(mft_ni, mrec); + if (unlikely(!ctx)) { + ntfs_error(vol->sb, "Failed to get search context."); + ret = -ENOMEM; + goto undo_alloc; + } + ret = ntfs_attr_lookup(mft_ni->type, mft_ni->name, mft_ni->name_len, + CASE_SENSITIVE, rl[1].vcn, NULL, 0, ctx); + if (unlikely(ret)) { + ntfs_error(vol->sb, "Failed to find last attribute extent of " + "mft data attribute."); + if (ret == -ENOENT) + ret = -EIO; + goto undo_alloc; + } + a = ctx->attr; + ll = sle64_to_cpu(a->data.non_resident.lowest_vcn); + /* Search back for the previous last allocated cluster of mft bitmap. */ + for (rl2 = rl; rl2 > mft_ni->runlist.rl; rl2--) { + if (ll >= rl2->vcn) + break; + } + BUG_ON(ll < rl2->vcn); + BUG_ON(ll >= rl2->vcn + rl2->length); + /* Get the size for the new mapping pairs array for this extent. */ + mp_size = ntfs_get_size_for_mapping_pairs(vol, rl2, ll); + if (unlikely(mp_size <= 0)) { + ntfs_error(vol->sb, "Get size for mapping pairs failed for " + "mft data attribute extent."); + ret = mp_size; + if (!ret) + ret = -EIO; + goto undo_alloc; + } + /* Expand the attribute record if necessary. */ + old_alen = le32_to_cpu(a->length); + ret = ntfs_attr_record_resize(ctx->mrec, a, mp_size + + le16_to_cpu(a->data.non_resident.mapping_pairs_offset)); + if (unlikely(ret)) { + if (ret != -ENOSPC) { + ntfs_error(vol->sb, "Failed to resize attribute " + "record for mft data attribute."); + goto undo_alloc; + } + // TODO: Deal with this by moving this extent to a new mft + // record or by starting a new extent in a new mft record or by + // moving other attributes out of this mft record. + // Note: Use the special reserved mft records and ensure that + // this extent is not required to find the mft record in + // question. + ntfs_error(vol->sb, "Not enough space in this mft record to " + "accomodate extended mft data attribute " + "extent. Cannot handle this yet."); + ret = -EOPNOTSUPP; + goto undo_alloc; + } + mp_rebuilt = TRUE; + /* Generate the mapping pairs array directly into the attr record. */ + ret = ntfs_mapping_pairs_build(vol, (u8*)a + + le16_to_cpu(a->data.non_resident.mapping_pairs_offset), + mp_size, rl2, ll, NULL); + if (unlikely(ret)) { + ntfs_error(vol->sb, "Failed to build mapping pairs array of " + "mft data attribute."); + goto undo_alloc; + } + /* Update the highest_vcn. */ + a->data.non_resident.highest_vcn = cpu_to_sle64(rl[1].vcn - 1); + /* + * We now have extended the mft data allocated_size by nr clusters. + * Reflect this in the ntfs_inode structure and the attribute record. + * @rl is the last (non-terminator) runlist element of mft data + * attribute. + */ + if (a->data.non_resident.lowest_vcn) { + /* + * We are not in the first attribute extent, switch to it, but + * first ensure the changes will make it to disk later. + */ + flush_dcache_mft_record_page(ctx->ntfs_ino); + mark_mft_record_dirty(ctx->ntfs_ino); + ntfs_attr_reinit_search_ctx(ctx); + ret = ntfs_attr_lookup(mft_ni->type, mft_ni->name, + mft_ni->name_len, CASE_SENSITIVE, 0, NULL, 0, + ctx); + if (unlikely(ret)) { + ntfs_error(vol->sb, "Failed to find first attribute " + "extent of mft data attribute."); + goto restore_undo_alloc; + } + a = ctx->attr; + } + mft_ni->allocated_size += nr << vol->cluster_size_bits; + a->data.non_resident.allocated_size = + cpu_to_sle64(mft_ni->allocated_size); + /* Ensure the changes make it to disk. */ + flush_dcache_mft_record_page(ctx->ntfs_ino); + mark_mft_record_dirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(mft_ni); + up_write(&mft_ni->runlist.lock); + ntfs_debug("Done."); + return 0; +restore_undo_alloc: + ntfs_attr_reinit_search_ctx(ctx); + if (ntfs_attr_lookup(mft_ni->type, mft_ni->name, mft_ni->name_len, + CASE_SENSITIVE, rl[1].vcn, NULL, 0, ctx)) { + ntfs_error(vol->sb, "Failed to find last attribute extent of " + "mft data attribute.%s", es); + mft_ni->allocated_size += nr << vol->cluster_size_bits; + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(mft_ni); + up_write(&mft_ni->runlist.lock); + /* + * The only thing that is now wrong is ->allocated_size of the + * base attribute extent which chkdsk should be able to fix. + */ + NVolSetErrors(vol); + return ret; + } + a = ctx->attr; + a->data.non_resident.highest_vcn = cpu_to_sle64(old_last_vcn - 1); +undo_alloc: + if (ntfs_cluster_free(vol->mft_ino, old_last_vcn, -1) < 0) { + ntfs_error(vol->sb, "Failed to free clusters from mft data " + "attribute.%s", es); + NVolSetErrors(vol); + } + if (ntfs_rl_truncate_nolock(vol, &mft_ni->runlist, old_last_vcn)) { + ntfs_error(vol->sb, "Failed to truncate mft data attribute " + "runlist.%s", es); + NVolSetErrors(vol); + } + if (mp_rebuilt) { + if (ntfs_mapping_pairs_build(vol, (u8*)a + le16_to_cpu( + a->data.non_resident.mapping_pairs_offset), + old_alen - le16_to_cpu( + a->data.non_resident.mapping_pairs_offset), + rl2, ll, NULL)) { + ntfs_error(vol->sb, "Failed to restore mapping pairs " + "array.%s", es); + NVolSetErrors(vol); + } + if (ntfs_attr_record_resize(ctx->mrec, a, old_alen)) { + ntfs_error(vol->sb, "Failed to restore attribute " + "record.%s", es); + NVolSetErrors(vol); + } + flush_dcache_mft_record_page(ctx->ntfs_ino); + mark_mft_record_dirty(ctx->ntfs_ino); + } + if (ctx) + ntfs_attr_put_search_ctx(ctx); + if (!IS_ERR(mrec)) + unmap_mft_record(mft_ni); + up_write(&mft_ni->runlist.lock); + return ret; +} + +/** + * ntfs_mft_record_layout - layout an mft record into a memory buffer + * @vol: volume to which the mft record will belong + * @mft_no: mft reference specifying the mft record number + * @m: destination buffer of size >= @vol->mft_record_size bytes + * + * Layout an empty, unused mft record with the mft record number @mft_no into + * the buffer @m. The volume @vol is needed because the mft record structure + * was modified in NTFS 3.1 so we need to know which volume version this mft + * record will be used on. + * + * Return 0 on success and -errno on error. + */ +static int ntfs_mft_record_layout(const ntfs_volume *vol, const s64 mft_no, + MFT_RECORD *m) +{ + ATTR_RECORD *a; + + ntfs_debug("Entering for mft record 0x%llx.", (long long)mft_no); + if (mft_no >= (1ll << 32)) { + ntfs_error(vol->sb, "Mft record number 0x%llx exceeds " + "maximum of 2^32.", (long long)mft_no); + return -ERANGE; + } + /* Start by clearing the whole mft record to gives us a clean slate. */ + memset(m, 0, vol->mft_record_size); + /* Aligned to 2-byte boundary. */ + if (vol->major_ver < 3 || (vol->major_ver == 3 && !vol->minor_ver)) + m->usa_ofs = cpu_to_le16((sizeof(MFT_RECORD_OLD) + 1) & ~1); + else { + m->usa_ofs = cpu_to_le16((sizeof(MFT_RECORD) + 1) & ~1); + /* + * Set the NTFS 3.1+ specific fields while we know that the + * volume version is 3.1+. + */ + m->reserved = 0; + m->mft_record_number = cpu_to_le32((u32)mft_no); + } + m->magic = magic_FILE; + if (vol->mft_record_size >= NTFS_BLOCK_SIZE) + m->usa_count = cpu_to_le16(vol->mft_record_size / + NTFS_BLOCK_SIZE + 1); + else { + m->usa_count = cpu_to_le16(1); + ntfs_warning(vol->sb, "Sector size is bigger than mft record " + "size. Setting usa_count to 1. If chkdsk " + "reports this as corruption, please email " + "linux-ntfs-dev@lists.sourceforge.net stating " + "that you saw this message and that the " + "modified file system created was corrupt. " + "Thank you."); + } + /* Set the update sequence number to 1. */ + *(le16*)((u8*)m + le16_to_cpu(m->usa_ofs)) = cpu_to_le16(1); + m->lsn = 0; + m->sequence_number = cpu_to_le16(1); + m->link_count = 0; + /* + * Place the attributes straight after the update sequence array, + * aligned to 8-byte boundary. + */ + m->attrs_offset = cpu_to_le16((le16_to_cpu(m->usa_ofs) + + (le16_to_cpu(m->usa_count) << 1) + 7) & ~7); + m->flags = 0; + /* + * Using attrs_offset plus eight bytes (for the termination attribute). + * attrs_offset is already aligned to 8-byte boundary, so no need to + * align again. + */ + m->bytes_in_use = cpu_to_le32(le16_to_cpu(m->attrs_offset) + 8); + m->bytes_allocated = cpu_to_le32(vol->mft_record_size); + m->base_mft_record = 0; + m->next_attr_instance = 0; + /* Add the termination attribute. */ + a = (ATTR_RECORD*)((u8*)m + le16_to_cpu(m->attrs_offset)); + a->type = AT_END; + a->length = 0; + ntfs_debug("Done."); + return 0; +} + +/** + * ntfs_mft_record_format - format an mft record on an ntfs volume + * @vol: volume on which to format the mft record + * @mft_no: mft record number to format + * + * Format the mft record @mft_no in $MFT/$DATA, i.e. lay out an empty, unused + * mft record into the appropriate place of the mft data attribute. This is + * used when extending the mft data attribute. + * + * Return 0 on success and -errno on error. + */ +static int ntfs_mft_record_format(const ntfs_volume *vol, const s64 mft_no) +{ + struct inode *mft_vi = vol->mft_ino; + struct page *page; + MFT_RECORD *m; + pgoff_t index, end_index; + unsigned int ofs; + int err; + + ntfs_debug("Entering for mft record 0x%llx.", (long long)mft_no); + /* + * The index into the page cache and the offset within the page cache + * page of the wanted mft record. + */ + index = mft_no << vol->mft_record_size_bits >> PAGE_CACHE_SHIFT; + ofs = (mft_no << vol->mft_record_size_bits) & ~PAGE_CACHE_MASK; + /* The maximum valid index into the page cache for $MFT's data. */ + end_index = mft_vi->i_size >> PAGE_CACHE_SHIFT; + if (unlikely(index >= end_index)) { + if (unlikely(index > end_index || ofs + vol->mft_record_size >= + (mft_vi->i_size & ~PAGE_CACHE_MASK))) { + ntfs_error(vol->sb, "Tried to format non-existing mft " + "record 0x%llx.", (long long)mft_no); + return -ENOENT; + } + } + /* Read, map, and pin the page containing the mft record. */ + page = ntfs_map_page(mft_vi->i_mapping, index); + if (unlikely(IS_ERR(page))) { + ntfs_error(vol->sb, "Failed to map page containing mft record " + "to format 0x%llx.", (long long)mft_no); + return PTR_ERR(page); + } + lock_page(page); + BUG_ON(!PageUptodate(page)); + ClearPageUptodate(page); + m = (MFT_RECORD*)((u8*)page_address(page) + ofs); + err = ntfs_mft_record_layout(vol, mft_no, m); + if (unlikely(err)) { + ntfs_error(vol->sb, "Failed to layout mft record 0x%llx.", + (long long)mft_no); + SetPageUptodate(page); + unlock_page(page); + ntfs_unmap_page(page); + return err; + } + flush_dcache_page(page); + SetPageUptodate(page); + unlock_page(page); + /* + * Make sure the mft record is written out to disk. We could use + * ilookup5() to check if an inode is in icache and so on but this is + * unnecessary as ntfs_writepage() will write the dirty record anyway. + */ + mark_ntfs_record_dirty(page, ofs); + ntfs_unmap_page(page); + ntfs_debug("Done."); + return 0; +} + +/** + * ntfs_mft_record_alloc - allocate an mft record on an ntfs volume + * @vol: [IN] volume on which to allocate the mft record + * @mode: [IN] mode if want a file or directory, i.e. base inode or 0 + * @base_ni: [IN] open base inode if allocating an extent mft record or NULL + * @mrec: [OUT] on successful return this is the mapped mft record + * + * Allocate an mft record in $MFT/$DATA of an open ntfs volume @vol. + * + * If @base_ni is NULL make the mft record a base mft record, i.e. a file or + * direvctory inode, and allocate it at the default allocator position. In + * this case @mode is the file mode as given to us by the caller. We in + * particular use @mode to distinguish whether a file or a directory is being + * created (S_IFDIR(mode) and S_IFREG(mode), respectively). + * + * If @base_ni is not NULL make the allocated mft record an extent record, + * allocate it starting at the mft record after the base mft record and attach + * the allocated and opened ntfs inode to the base inode @base_ni. In this + * case @mode must be 0 as it is meaningless for extent inodes. + * + * You need to check the return value with IS_ERR(). If false, the function + * was successful and the return value is the now opened ntfs inode of the + * allocated mft record. *@mrec is then set to the allocated, mapped, pinned, + * and locked mft record. If IS_ERR() is true, the function failed and the + * error code is obtained from PTR_ERR(return value). *@mrec is undefined in + * this case. + * + * Allocation strategy: + * + * To find a free mft record, we scan the mft bitmap for a zero bit. To + * optimize this we start scanning at the place specified by @base_ni or if + * @base_ni is NULL we start where we last stopped and we perform wrap around + * when we reach the end. Note, we do not try to allocate mft records below + * number 24 because numbers 0 to 15 are the defined system files anyway and 16 + * to 24 are special in that they are used for storing extension mft records + * for the $DATA attribute of $MFT. This is required to avoid the possibility + * of creating a runlist with a circular dependency which once written to disk + * can never be read in again. Windows will only use records 16 to 24 for + * normal files if the volume is completely out of space. We never use them + * which means that when the volume is really out of space we cannot create any + * more files while Windows can still create up to 8 small files. We can start + * doing this at some later time, it does not matter much for now. + * + * When scanning the mft bitmap, we only search up to the last allocated mft + * record. If there are no free records left in the range 24 to number of + * allocated mft records, then we extend the $MFT/$DATA attribute in order to + * create free mft records. We extend the allocated size of $MFT/$DATA by 16 + * records at a time or one cluster, if cluster size is above 16kiB. If there + * is not sufficient space to do this, we try to extend by a single mft record + * or one cluster, if cluster size is above the mft record size. + * + * No matter how many mft records we allocate, we initialize only the first + * allocated mft record, incrementing mft data size and initialized size + * accordingly, open an ntfs_inode for it and return it to the caller, unless + * there are less than 24 mft records, in which case we allocate and initialize + * mft records until we reach record 24 which we consider as the first free mft + * record for use by normal files. + * + * If during any stage we overflow the initialized data in the mft bitmap, we + * extend the initialized size (and data size) by 8 bytes, allocating another + * cluster if required. The bitmap data size has to be at least equal to the + * number of mft records in the mft, but it can be bigger, in which case the + * superflous bits are padded with zeroes. + * + * Thus, when we return successfully (IS_ERR() is false), we will have: + * - initialized / extended the mft bitmap if necessary, + * - initialized / extended the mft data if necessary, + * - set the bit corresponding to the mft record being allocated in the + * mft bitmap, + * - opened an ntfs_inode for the allocated mft record, and we will have + * - returned the ntfs_inode as well as the allocated mapped, pinned, and + * locked mft record. + * + * On error, the volume will be left in a consistent state and no record will + * be allocated. If rolling back a partial operation fails, we may leave some + * inconsistent metadata in which case we set NVolErrors() so the volume is + * left dirty when unmounted. + * + * Note, this function cannot make use of most of the normal functions, like + * for example for attribute resizing, etc, because when the run list overflows + * the base mft record and an attribute list is used, it is very important that + * the extension mft records used to store the $DATA attribute of $MFT can be + * reached without having to read the information contained inside them, as + * this would make it impossible to find them in the first place after the + * volume is unmounted. $MFT/$BITMAP probably does not need to follow this + * rule because the bitmap is not essential for finding the mft records, but on + * the other hand, handling the bitmap in this special way would make life + * easier because otherwise there might be circular invocations of functions + * when reading the bitmap. + */ +ntfs_inode *ntfs_mft_record_alloc(ntfs_volume *vol, const int mode, + ntfs_inode *base_ni, MFT_RECORD **mrec) +{ + s64 ll, bit, old_data_initialized, old_data_size; + struct inode *vi; + struct page *page; + ntfs_inode *mft_ni, *mftbmp_ni, *ni; + ntfs_attr_search_ctx *ctx; + MFT_RECORD *m; + ATTR_RECORD *a; + pgoff_t index; + unsigned int ofs; + int err; + le16 seq_no, usn; + BOOL record_formatted = FALSE; + + if (base_ni) { + ntfs_debug("Entering (allocating an extent mft record for " + "base mft record 0x%llx).", + (long long)base_ni->mft_no); + /* @mode and @base_ni are mutually exclusive. */ + BUG_ON(mode); + } else + ntfs_debug("Entering (allocating a base mft record)."); + if (mode) { + /* @mode and @base_ni are mutually exclusive. */ + BUG_ON(base_ni); + /* We only support creation of normal files and directories. */ + if (!S_ISREG(mode) && !S_ISDIR(mode)) + return ERR_PTR(-EOPNOTSUPP); + } + BUG_ON(!mrec); + mft_ni = NTFS_I(vol->mft_ino); + mftbmp_ni = NTFS_I(vol->mftbmp_ino); + down_write(&vol->mftbmp_lock); + bit = ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(vol, base_ni); + if (bit >= 0) { + ntfs_debug("Found and allocated free record (#1), bit 0x%llx.", + (long long)bit); + goto have_alloc_rec; + } + if (bit != -ENOSPC) { + up_write(&vol->mftbmp_lock); + return ERR_PTR(bit); + } + /* + * No free mft records left. If the mft bitmap already covers more + * than the currently used mft records, the next records are all free, + * so we can simply allocate the first unused mft record. + * Note: We also have to make sure that the mft bitmap at least covers + * the first 24 mft records as they are special and whilst they may not + * be in use, we do not allocate from them. + */ + ll = mft_ni->initialized_size >> vol->mft_record_size_bits; + if (mftbmp_ni->initialized_size << 3 > ll && + mftbmp_ni->initialized_size > 3) { + bit = ll; + if (bit < 24) + bit = 24; + if (unlikely(bit >= (1ll << 32))) + goto max_err_out; + ntfs_debug("Found free record (#2), bit 0x%llx.", + (long long)bit); + goto found_free_rec; + } + /* + * The mft bitmap needs to be expanded until it covers the first unused + * mft record that we can allocate. + * Note: The smallest mft record we allocate is mft record 24. + */ + bit = mftbmp_ni->initialized_size << 3; + if (unlikely(bit >= (1ll << 32))) + goto max_err_out; + ntfs_debug("Status of mftbmp before extension: allocated_size 0x%llx, " + "data_size 0x%llx, initialized_size 0x%llx.", + (long long)mftbmp_ni->allocated_size, + (long long)vol->mftbmp_ino->i_size, + (long long)mftbmp_ni->initialized_size); + if (mftbmp_ni->initialized_size + 8 > mftbmp_ni->allocated_size) { + /* Need to extend bitmap by one more cluster. */ + ntfs_debug("mftbmp: initialized_size + 8 > allocated_size."); + err = ntfs_mft_bitmap_extend_allocation_nolock(vol); + if (unlikely(err)) { + up_write(&vol->mftbmp_lock); + goto err_out; + } + ntfs_debug("Status of mftbmp after allocation extension: " + "allocated_size 0x%llx, data_size 0x%llx, " + "initialized_size 0x%llx.", + (long long)mftbmp_ni->allocated_size, + (long long)vol->mftbmp_ino->i_size, + (long long)mftbmp_ni->initialized_size); + } + /* + * We now have sufficient allocated space, extend the initialized_size + * as well as the data_size if necessary and fill the new space with + * zeroes. + */ + err = ntfs_mft_bitmap_extend_initialized_nolock(vol); + if (unlikely(err)) { + up_write(&vol->mftbmp_lock); + goto err_out; + } + ntfs_debug("Status of mftbmp after initialized extention: " + "allocated_size 0x%llx, data_size 0x%llx, " + "initialized_size 0x%llx.", + (long long)mftbmp_ni->allocated_size, + (long long)vol->mftbmp_ino->i_size, + (long long)mftbmp_ni->initialized_size); + ntfs_debug("Found free record (#3), bit 0x%llx.", (long long)bit); +found_free_rec: + /* @bit is the found free mft record, allocate it in the mft bitmap. */ + ntfs_debug("At found_free_rec."); + err = ntfs_bitmap_set_bit(vol->mftbmp_ino, bit); + if (unlikely(err)) { + ntfs_error(vol->sb, "Failed to allocate bit in mft bitmap."); + up_write(&vol->mftbmp_lock); + goto err_out; + } + ntfs_debug("Set bit 0x%llx in mft bitmap.", (long long)bit); +have_alloc_rec: + /* + * The mft bitmap is now uptodate. Deal with mft data attribute now. + * Note, we keep hold of the mft bitmap lock for writing until all + * modifications to the mft data attribute are complete, too, as they + * will impact decisions for mft bitmap and mft record allocation done + * by a parallel allocation and if the lock is not maintained a + * parallel allocation could allocate the same mft record as this one. + */ + ll = (bit + 1) << vol->mft_record_size_bits; + if (ll <= mft_ni->initialized_size) { + ntfs_debug("Allocated mft record already initialized."); + goto mft_rec_already_initialized; + } + ntfs_debug("Initializing allocated mft record."); + /* + * The mft record is outside the initialized data. Extend the mft data + * attribute until it covers the allocated record. The loop is only + * actually traversed more than once when a freshly formatted volume is + * first written to so it optimizes away nicely in the common case. + */ + ntfs_debug("Status of mft data before extension: " + "allocated_size 0x%llx, data_size 0x%llx, " + "initialized_size 0x%llx.", + (long long)mft_ni->allocated_size, + (long long)vol->mft_ino->i_size, + (long long)mft_ni->initialized_size); + while (ll > mft_ni->allocated_size) { + err = ntfs_mft_data_extend_allocation_nolock(vol); + if (unlikely(err)) { + ntfs_error(vol->sb, "Failed to extend mft data " + "allocation."); + goto undo_mftbmp_alloc_nolock; + } + ntfs_debug("Status of mft data after allocation extension: " + "allocated_size 0x%llx, data_size 0x%llx, " + "initialized_size 0x%llx.", + (long long)mft_ni->allocated_size, + (long long)vol->mft_ino->i_size, + (long long)mft_ni->initialized_size); + } + /* + * Extend mft data initialized size (and data size of course) to reach + * the allocated mft record, formatting the mft records allong the way. + * Note: We only modify the ntfs_inode structure as that is all that is + * needed by ntfs_mft_record_format(). We will update the attribute + * record itself in one fell swoop later on. + */ + old_data_initialized = mft_ni->initialized_size; + old_data_size = vol->mft_ino->i_size; + while (ll > mft_ni->initialized_size) { + s64 new_initialized_size, mft_no; + + new_initialized_size = mft_ni->initialized_size + + vol->mft_record_size; + mft_no = mft_ni->initialized_size >> vol->mft_record_size_bits; + if (new_initialized_size > vol->mft_ino->i_size) + vol->mft_ino->i_size = new_initialized_size; + ntfs_debug("Initializing mft record 0x%llx.", + (long long)mft_no); + err = ntfs_mft_record_format(vol, mft_no); + if (unlikely(err)) { + ntfs_error(vol->sb, "Failed to format mft record."); + goto undo_data_init; + } + mft_ni->initialized_size = new_initialized_size; + } + record_formatted = TRUE; + /* Update the mft data attribute record to reflect the new sizes. */ + m = map_mft_record(mft_ni); + if (IS_ERR(m)) { + ntfs_error(vol->sb, "Failed to map mft record."); + err = PTR_ERR(m); + goto undo_data_init; + } + ctx = ntfs_attr_get_search_ctx(mft_ni, m); + if (unlikely(!ctx)) { + ntfs_error(vol->sb, "Failed to get search context."); + err = -ENOMEM; + unmap_mft_record(mft_ni); + goto undo_data_init; + } + err = ntfs_attr_lookup(mft_ni->type, mft_ni->name, mft_ni->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx); + if (unlikely(err)) { + ntfs_error(vol->sb, "Failed to find first attribute extent of " + "mft data attribute."); + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(mft_ni); + goto undo_data_init; + } + a = ctx->attr; + a->data.non_resident.initialized_size = + cpu_to_sle64(mft_ni->initialized_size); + a->data.non_resident.data_size = cpu_to_sle64(vol->mft_ino->i_size); + /* Ensure the changes make it to disk. */ + flush_dcache_mft_record_page(ctx->ntfs_ino); + mark_mft_record_dirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(mft_ni); + ntfs_debug("Status of mft data after mft record initialization: " + "allocated_size 0x%llx, data_size 0x%llx, " + "initialized_size 0x%llx.", + (long long)mft_ni->allocated_size, + (long long)vol->mft_ino->i_size, + (long long)mft_ni->initialized_size); + BUG_ON(vol->mft_ino->i_size > mft_ni->allocated_size); + BUG_ON(mft_ni->initialized_size > vol->mft_ino->i_size); +mft_rec_already_initialized: + /* + * We can finally drop the mft bitmap lock as the mft data attribute + * has been fully updated. The only disparity left is that the + * allocated mft record still needs to be marked as in use to match the + * set bit in the mft bitmap but this is actually not a problem since + * this mft record is not referenced from anywhere yet and the fact + * that it is allocated in the mft bitmap means that no-one will try to + * allocate it either. + */ + up_write(&vol->mftbmp_lock); + /* + * We now have allocated and initialized the mft record. Calculate the + * index of and the offset within the page cache page the record is in. + */ + index = bit << vol->mft_record_size_bits >> PAGE_CACHE_SHIFT; + ofs = (bit << vol->mft_record_size_bits) & ~PAGE_CACHE_MASK; + /* Read, map, and pin the page containing the mft record. */ + page = ntfs_map_page(vol->mft_ino->i_mapping, index); + if (unlikely(IS_ERR(page))) { + ntfs_error(vol->sb, "Failed to map page containing allocated " + "mft record 0x%llx.", (long long)bit); + err = PTR_ERR(page); + goto undo_mftbmp_alloc; + } + lock_page(page); + BUG_ON(!PageUptodate(page)); + ClearPageUptodate(page); + m = (MFT_RECORD*)((u8*)page_address(page) + ofs); + /* If we just formatted the mft record no need to do it again. */ + if (!record_formatted) { + /* Sanity check that the mft record is really not in use. */ + if (ntfs_is_file_record(m->magic) && + (m->flags & MFT_RECORD_IN_USE)) { + ntfs_error(vol->sb, "Mft record 0x%llx was marked " + "free in mft bitmap but is marked " + "used itself. Corrupt filesystem. " + "Unmount and run chkdsk.", + (long long)bit); + err = -EIO; + SetPageUptodate(page); + unlock_page(page); + ntfs_unmap_page(page); + NVolSetErrors(vol); + goto undo_mftbmp_alloc; + } + /* + * We need to (re-)format the mft record, preserving the + * sequence number if it is not zero as well as the update + * sequence number if it is not zero or -1 (0xffff). This + * means we do not need to care whether or not something went + * wrong with the previous mft record. + */ + seq_no = m->sequence_number; + usn = *(le16*)((u8*)m + le16_to_cpu(m->usa_ofs)); + err = ntfs_mft_record_layout(vol, bit, m); + if (unlikely(err)) { + ntfs_error(vol->sb, "Failed to layout allocated mft " + "record 0x%llx.", (long long)bit); + SetPageUptodate(page); + unlock_page(page); + ntfs_unmap_page(page); + goto undo_mftbmp_alloc; + } + if (seq_no) + m->sequence_number = seq_no; + if (usn && le16_to_cpu(usn) != 0xffff) + *(le16*)((u8*)m + le16_to_cpu(m->usa_ofs)) = usn; + } + /* Set the mft record itself in use. */ + m->flags |= MFT_RECORD_IN_USE; + if (S_ISDIR(mode)) + m->flags |= MFT_RECORD_IS_DIRECTORY; + flush_dcache_page(page); + SetPageUptodate(page); + if (base_ni) { + /* + * Setup the base mft record in the extent mft record. This + * completes initialization of the allocated extent mft record + * and we can simply use it with map_extent_mft_record(). + */ + m->base_mft_record = MK_LE_MREF(base_ni->mft_no, + base_ni->seq_no); + /* + * Allocate an extent inode structure for the new mft record, + * attach it to the base inode @base_ni and map, pin, and lock + * its, i.e. the allocated, mft record. + */ + m = map_extent_mft_record(base_ni, bit, &ni); + if (IS_ERR(m)) { + ntfs_error(vol->sb, "Failed to map allocated extent " + "mft record 0x%llx.", (long long)bit); + err = PTR_ERR(m); + /* Set the mft record itself not in use. */ + m->flags &= cpu_to_le16( + ~le16_to_cpu(MFT_RECORD_IN_USE)); + flush_dcache_page(page); + /* Make sure the mft record is written out to disk. */ + mark_ntfs_record_dirty(page, ofs); + unlock_page(page); + ntfs_unmap_page(page); + goto undo_mftbmp_alloc; + } + /* + * Make sure the allocated mft record is written out to disk. + * No need to set the inode dirty because the caller is going + * to do that anyway after finishing with the new extent mft + * record (e.g. at a minimum a new attribute will be added to + * the mft record. + */ + mark_ntfs_record_dirty(page, ofs); + unlock_page(page); + /* + * Need to unmap the page since map_extent_mft_record() mapped + * it as well so we have it mapped twice at the moment. + */ + ntfs_unmap_page(page); + } else { + /* + * Allocate a new VFS inode and set it up. NOTE: @vi->i_nlink + * is set to 1 but the mft record->link_count is 0. The caller + * needs to bear this in mind. + */ + vi = new_inode(vol->sb); + if (unlikely(!vi)) { + err = -ENOMEM; + /* Set the mft record itself not in use. */ + m->flags &= cpu_to_le16( + ~le16_to_cpu(MFT_RECORD_IN_USE)); + flush_dcache_page(page); + /* Make sure the mft record is written out to disk. */ + mark_ntfs_record_dirty(page, ofs); + unlock_page(page); + ntfs_unmap_page(page); + goto undo_mftbmp_alloc; + } + vi->i_ino = bit; + /* + * This is the optimal IO size (for stat), not the fs block + * size. + */ + vi->i_blksize = PAGE_CACHE_SIZE; + /* + * This is for checking whether an inode has changed w.r.t. a + * file so that the file can be updated if necessary (compare + * with f_version). + */ + vi->i_version = 1; + + /* The owner and group come from the ntfs volume. */ + vi->i_uid = vol->uid; + vi->i_gid = vol->gid; + + /* Initialize the ntfs specific part of @vi. */ + ntfs_init_big_inode(vi); + ni = NTFS_I(vi); + /* + * Set the appropriate mode, attribute type, and name. For + * directories, also setup the index values to the defaults. + */ + if (S_ISDIR(mode)) { + vi->i_mode = S_IFDIR | S_IRWXUGO; + vi->i_mode &= ~vol->dmask; + + NInoSetMstProtected(ni); + ni->type = AT_INDEX_ALLOCATION; + ni->name = I30; + ni->name_len = 4; + + ni->itype.index.block_size = 4096; + ni->itype.index.block_size_bits = generic_ffs(4096) - 1; + ni->itype.index.collation_rule = COLLATION_FILE_NAME; + if (vol->cluster_size <= ni->itype.index.block_size) { + ni->itype.index.vcn_size = vol->cluster_size; + ni->itype.index.vcn_size_bits = + vol->cluster_size_bits; + } else { + ni->itype.index.vcn_size = vol->sector_size; + ni->itype.index.vcn_size_bits = + vol->sector_size_bits; + } + } else { + vi->i_mode = S_IFREG | S_IRWXUGO; + vi->i_mode &= ~vol->fmask; + + ni->type = AT_DATA; + ni->name = NULL; + ni->name_len = 0; + } + if (IS_RDONLY(vi)) + vi->i_mode &= ~S_IWUGO; + + /* Set the inode times to the current time. */ + vi->i_atime = vi->i_mtime = vi->i_ctime = current_kernel_time(); + /* + * Set the file size to 0, the ntfs inode sizes are set to 0 by + * the call to ntfs_init_big_inode() below. + */ + vi->i_size = 0; + vi->i_blocks = 0; + + /* Set the sequence number. */ + vi->i_generation = ni->seq_no = le16_to_cpu(m->sequence_number); + /* + * Manually map, pin, and lock the mft record as we already + * have its page mapped and it is very easy to do. + */ + atomic_inc(&ni->count); + down(&ni->mrec_lock); + ni->page = page; + ni->page_ofs = ofs; + /* + * Make sure the allocated mft record is written out to disk. + * NOTE: We do not set the ntfs inode dirty because this would + * fail in ntfs_write_inode() because the inode does not have a + * standard information attribute yet. Also, there is no need + * to set the inode dirty because the caller is going to do + * that anyway after finishing with the new mft record (e.g. at + * a minimum some new attributes will be added to the mft + * record. + */ + mark_ntfs_record_dirty(page, ofs); + unlock_page(page); + + /* Add the inode to the inode hash for the superblock. */ + insert_inode_hash(vi); + + /* Update the default mft allocation position. */ + vol->mft_data_pos = bit + 1; + } + /* + * Return the opened, allocated inode of the allocated mft record as + * well as the mapped, pinned, and locked mft record. + */ + ntfs_debug("Returning opened, allocated %sinode 0x%llx.", + base_ni ? "extent " : "", (long long)bit); + *mrec = m; + return ni; +undo_data_init: + mft_ni->initialized_size = old_data_initialized; + vol->mft_ino->i_size = old_data_size; + goto undo_mftbmp_alloc_nolock; +undo_mftbmp_alloc: + down_write(&vol->mftbmp_lock); +undo_mftbmp_alloc_nolock: + if (ntfs_bitmap_clear_bit(vol->mftbmp_ino, bit)) { + ntfs_error(vol->sb, "Failed to clear bit in mft bitmap.%s", es); + NVolSetErrors(vol); + } + up_write(&vol->mftbmp_lock); +err_out: + return ERR_PTR(err); +max_err_out: + ntfs_warning(vol->sb, "Cannot allocate mft record because the maximum " + "number of inodes (2^32) has already been reached."); + up_write(&vol->mftbmp_lock); + return ERR_PTR(-ENOSPC); +} + /** * ntfs_extent_mft_record_free - free an extent mft record on an ntfs volume * @ni: ntfs inode of the mapped extent mft record to free diff --git a/fs/ntfs/mft.h b/fs/ntfs/mft.h index c2c7a018c3f9..407de2cef1d6 100644 --- a/fs/ntfs/mft.h +++ b/fs/ntfs/mft.h @@ -118,6 +118,8 @@ extern BOOL ntfs_may_write_mft_record(ntfs_volume *vol, const unsigned long mft_no, const MFT_RECORD *m, ntfs_inode **locked_ni); +extern ntfs_inode *ntfs_mft_record_alloc(ntfs_volume *vol, const int mode, + ntfs_inode *base_ni, MFT_RECORD **mrec); extern int ntfs_extent_mft_record_free(ntfs_inode *ni, MFT_RECORD *m); #endif /* NTFS_RW */ -- cgit v1.2.3 From 30788d3f6a60e7f8cbc006c8b7cbb31cdcb242ec Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Sun, 17 Oct 2004 13:56:48 +0100 Subject: NTFS: 2.1.21 release Signed-off-by: Anton Altaparmakov --- Documentation/filesystems/ntfs.txt | 4 ++++ fs/ntfs/ChangeLog | 4 ++-- fs/ntfs/Makefile | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Documentation/filesystems/ntfs.txt b/Documentation/filesystems/ntfs.txt index 7d600c0f4c86..970e54f04e89 100644 --- a/Documentation/filesystems/ntfs.txt +++ b/Documentation/filesystems/ntfs.txt @@ -277,6 +277,10 @@ ChangeLog Note, a technical ChangeLog aimed at kernel hackers is in fs/ntfs/ChangeLog. +2.1.21: + - Fix several race conditions and various other bugs. + - Many internal cleanups, code reorganization, optimizations, and mft + and index record writing code rewritten to fit in with the changes. 2.1.20: - Fix two stupid bugs introduced in 2.1.18 release. 2.1.19: diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 218b4c8bb947..d526921af13e 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -21,7 +21,7 @@ ToDo/Notes: - Enable the code for setting the NT4 compatibility flag when we start making NTFS 1.2 specific modifications. -2.1.21-WIP +2.1.21 - Fix some races and bugs, rewrite mft write code, add mft allocator. - Implement extent mft record deallocation fs/ntfs/mft.c::ntfs_extent_mft_record_free(). @@ -115,7 +115,7 @@ ToDo/Notes: fs/ntfs/inode.c::ntfs_clear_big_inode(). (Thanks to Christoph Hellwig for spotting this.) - Fix race condition in fs/ntfs/inode.c::ntfs_put_inode() by taking the - inode semaphore around the code thst sets ni->itype.index.bmp_ino to + inode semaphore around the code that sets ni->itype.index.bmp_ino to NULL and reorganize the code to optimize it a bit. (Thanks to Christoph Hellwig for spotting this.) - Modify fs/ntfs/aops.c::mark_ntfs_record_dirty() to no longer take the diff --git a/fs/ntfs/Makefile b/fs/ntfs/Makefile index 94f0775898a4..99cac1cd4285 100644 --- a/fs/ntfs/Makefile +++ b/fs/ntfs/Makefile @@ -6,7 +6,7 @@ ntfs-objs := aops.o attrib.o collate.o compress.o debug.o dir.o file.o \ index.o inode.o mft.o mst.o namei.o runlist.o super.o sysctl.o \ unistr.o upcase.o -EXTRA_CFLAGS = -DNTFS_VERSION=\"2.1.21-WIP\" +EXTRA_CFLAGS = -DNTFS_VERSION=\"2.1.21\" ifeq ($(CONFIG_NTFS_DEBUG),y) EXTRA_CFLAGS += -DDEBUG -- cgit v1.2.3 From b5b2b4a87dfbb15a31e78b0da139559afe78a05a Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Mon, 18 Oct 2004 11:25:49 +0100 Subject: USB SpeedTouch / ATM: Make it work on 64-bit hosts. Reduce size of struct udsl_control to make it fit in skb->cb, by dropping the cell_header and generating it later instead of storing it. Signed-off-by: David Woodhouse Signed-off-by: Duncan Sands Signed-off-by: Greg Kroah-Hartman --- drivers/usb/atm/usb_atm.c | 20 ++++++++++++-------- drivers/usb/atm/usb_atm.h | 1 - 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/drivers/usb/atm/usb_atm.c b/drivers/usb/atm/usb_atm.c index 9180dda04fc5..8d1d365846a1 100644 --- a/drivers/usb/atm/usb_atm.c +++ b/drivers/usb/atm/usb_atm.c @@ -311,6 +311,15 @@ static void udsl_extract_cells(struct udsl_instance_data *instance, ** encode ** *************/ +static inline void udsl_fill_cell_header(unsigned char *target, struct atm_vcc *vcc) +{ + target[0] = vcc->vpi >> 4; + target[1] = (vcc->vpi << 4) | (vcc->vci >> 12); + target[2] = vcc->vci >> 4; + target[3] = vcc->vci << 4; + target[4] = 0xec; +} + static const unsigned char zeros[ATM_CELL_PAYLOAD]; static void udsl_groom_skb(struct atm_vcc *vcc, struct sk_buff *skb) @@ -320,11 +329,6 @@ static void udsl_groom_skb(struct atm_vcc *vcc, struct sk_buff *skb) u32 crc; ctrl->atm_data.vcc = vcc; - ctrl->cell_header[0] = vcc->vpi >> 4; - ctrl->cell_header[1] = (vcc->vpi << 4) | (vcc->vci >> 12); - ctrl->cell_header[2] = vcc->vci >> 4; - ctrl->cell_header[3] = vcc->vci << 4; - ctrl->cell_header[4] = 0xec; ctrl->num_cells = UDSL_NUM_CELLS(skb->len); ctrl->num_entire = skb->len / ATM_CELL_PAYLOAD; @@ -366,7 +370,7 @@ static unsigned int udsl_write_cells(struct udsl_instance_data *instance, ne = min(howmany, ctrl->num_entire); for (i = 0; i < ne; i++) { - memcpy(target, ctrl->cell_header, ATM_CELL_HEADER); + udsl_fill_cell_header(target, ctrl->atm_data.vcc); target += ATM_CELL_HEADER; memcpy(target, skb->data, ATM_CELL_PAYLOAD); target += ATM_CELL_PAYLOAD; @@ -386,7 +390,7 @@ static unsigned int udsl_write_cells(struct udsl_instance_data *instance, memset(target, 0, instance->snd_padding); target += instance->snd_padding; } - memcpy(target, ctrl->cell_header, ATM_CELL_HEADER); + udsl_fill_cell_header(target, ctrl->atm_data.vcc); target += ATM_CELL_HEADER; memcpy(target, skb->data, skb->len); target += skb->len; @@ -400,7 +404,7 @@ static unsigned int udsl_write_cells(struct udsl_instance_data *instance, goto out; } - memcpy(target, ctrl->cell_header, ATM_CELL_HEADER); + udsl_fill_cell_header(target, ctrl->atm_data.vcc); target += ATM_CELL_HEADER; memset(target, 0, ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER); target += ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER; diff --git a/drivers/usb/atm/usb_atm.h b/drivers/usb/atm/usb_atm.h index 188e9171073e..219763cc3242 100644 --- a/drivers/usb/atm/usb_atm.h +++ b/drivers/usb/atm/usb_atm.h @@ -91,7 +91,6 @@ struct udsl_control { unsigned int num_cells; unsigned int num_entire; unsigned int pdu_padding; - unsigned char cell_header[ATM_CELL_HEADER]; unsigned char aal5_trailer[ATM_AAL5_TRAILER]; }; -- cgit v1.2.3 From 1881642411926cea530c2c9a23b5c93589cc2274 Mon Sep 17 00:00:00 2001 From: Duncan Sands Date: Mon, 18 Oct 2004 11:29:11 +0100 Subject: USB SpeedTouch cleanup. - Don't free pages with kfree() - Clean up debugging messages - Print name of firmware file loaded. Signed-off-by: Duncan Sands Signed-off-by: David Woodhouse Signed-off-by: Greg Kroah-Hartman --- drivers/usb/atm/speedtch.c | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/drivers/usb/atm/speedtch.c b/drivers/usb/atm/speedtch.c index 13d0a50cadfc..f17e576d12ec 100644 --- a/drivers/usb/atm/speedtch.c +++ b/drivers/usb/atm/speedtch.c @@ -165,8 +165,7 @@ static void speedtch_got_firmware(struct speedtch_instance_data *instance, goto out; } if ((err = usb_set_interface(instance->u.usb_dev, 1, 1)) < 0) { - dbg("speedtch_got_firmware: usb_set_interface returned %d!", - err); + dbg("speedtch_got_firmware: usb_set_interface returned %d!", err); instance->u.status = UDSL_NO_FIRMWARE; goto out; } @@ -572,7 +571,7 @@ static void speedtch_upload_firmware(struct speedtch_instance_data *instance, /* Start modem synchronisation */ if (speedtch_start_synchro(instance)) - dbg("speedtch_start_synchro: failed\n"); + dbg("speedtch_start_synchro: failed"); speedtch_got_firmware(instance, 1); @@ -585,7 +584,7 @@ static void speedtch_upload_firmware(struct speedtch_instance_data *instance, the firmware themselves */ usb_driver_release_interface(&speedtch_usb_driver, intf); fail_free: - kfree(buffer); + free_page((unsigned long)buffer); fail: speedtch_got_firmware(instance, 0); } @@ -596,28 +595,30 @@ static int speedtch_find_firmware(struct speedtch_instance_data { char buf[24]; const u16 bcdDevice = instance->u.usb_dev->descriptor.bcdDevice; + const u8 major_revision = bcdDevice >> 8; + const u8 minor_revision = bcdDevice & 0xff; - sprintf(buf, "speedtch-%d.bin.%x.%02x", phase, bcdDevice >> 8, - bcdDevice & 0xff); - dbg("speedtch_find_firmware: looking for %s\n", buf); + sprintf(buf, "speedtch-%d.bin.%x.%02x", phase, major_revision, minor_revision); + dbg("speedtch_find_firmware: looking for %s", buf); - if (!request_firmware(fw_p, buf, &instance->u.usb_dev->dev)) - return 0; + if (request_firmware(fw_p, buf, &instance->u.usb_dev->dev)) { + sprintf(buf, "speedtch-%d.bin.%x", phase, major_revision); + dbg("speedtch_find_firmware: looking for %s", buf); - sprintf(buf, "speedtch-%d.bin.%x", phase, bcdDevice >> 8); - dbg("speedtch_find_firmware: looking for %s\n", buf); + if (request_firmware(fw_p, buf, &instance->u.usb_dev->dev)) { + sprintf(buf, "speedtch-%d.bin", phase); + dbg("speedtch_find_firmware: looking for %s", buf); - if (!request_firmware(fw_p, buf, &instance->u.usb_dev->dev)) - return 0; - - sprintf(buf, "speedtch-%d.bin", phase); - dbg("speedtch_find_firmware: looking for %s\n", buf); + if (request_firmware(fw_p, buf, &instance->u.usb_dev->dev)) { + dev_warn(&instance->u.usb_dev->dev, "no stage %d firmware found!", phase); + return -ENOENT; + } + } + } - if (!request_firmware(fw_p, buf, &instance->u.usb_dev->dev)) - return 0; + dev_info(&instance->u.usb_dev->dev, "found stage %d firmware %s\n", phase, buf); - dev_warn(&instance->u.usb_dev->dev, "no stage %d firmware found!", phase); - return -ENOENT; + return 0; } static int speedtch_load_firmware(void *arg) -- cgit v1.2.3 From c638b507f22df9d6c61bfe596574dadbc20de314 Mon Sep 17 00:00:00 2001 From: Phil Dibowitz Date: Sun, 17 Oct 2004 23:55:12 -0700 Subject: [PATCH] USB Storage: Unusual_dev patch for Finepix 1300 and 1400's. There are several cameras (1300s and 1400s) with the same bcdDevice number (1000). Most of them are 8070 devices, but some of them are UFI devices (similar to 8070 devices but don't clear the sense data after an INQUIRY or REQUEST SENSE). Furthermore if the devices that truely do act like 8070 devices are overridden to be UFI, they find more than one LUN. Specifiying UFI and SINGLE_LUN seems to satisfy all kinds of devices that claim device number 1000. From: Pavel Machek Signed-off-by: Phil Dibowitz Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/unusual_devs.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h index 1d5181430529..1712773edaa1 100644 --- a/drivers/usb/storage/unusual_devs.h +++ b/drivers/usb/storage/unusual_devs.h @@ -142,10 +142,13 @@ UNUSUAL_DEV( 0x04b8, 0x0602, 0x0110, 0x0110, "785EPX Storage", US_SC_SCSI, US_PR_BULK, NULL, US_FL_SINGLE_LUN), +/* Not sure who reported this originally but + * Pavel Machek reported that the extra US_FL_SINGLE_LUN + * flag be added */ UNUSUAL_DEV( 0x04cb, 0x0100, 0x0000, 0x2210, "Fujifilm", "FinePix 1400Zoom", - US_SC_UFI, US_PR_DEVICE, NULL, US_FL_FIX_INQUIRY), + US_SC_UFI, US_PR_DEVICE, NULL, US_FL_FIX_INQUIRY | US_FL_SINGLE_LUN), /* Reported by Peter Wächtler * The device needs the flags only. -- cgit v1.2.3 From 62aaf0a199c253469dd7a9b8ea340ec5881dd184 Mon Sep 17 00:00:00 2001 From: Phil Dibowitz Date: Sun, 17 Oct 2004 23:55:35 -0700 Subject: [PATCH] USB Storage: unusual_devs patch for new tekom entry Stephan Fuhrmann sent in the entry for a device needing the new RESIDUE flag. Here is an appropriate tested patch. From: Stephan Fuhrmann Signed-off-by: Phil Dibowitz Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/unusual_devs.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h index 1712773edaa1..045078d2de5e 100644 --- a/drivers/usb/storage/unusual_devs.h +++ b/drivers/usb/storage/unusual_devs.h @@ -278,6 +278,14 @@ UNUSUAL_DEV( 0x052b, 0x1807, 0x0100, 0x0100, US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_IGNORE_RESIDUE ), +/* Yakumo Mega Image 37 + * Submitted by Stephan Fuhrmann */ +UNUSUAL_DEV( 0x052b, 0x1801, 0x0100, 0x0100, + "Tekom Technologies, Inc", + "300_CAMERA", + US_SC_DEVICE, US_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + /* This entry is needed because the device reports Sub=ff */ UNUSUAL_DEV( 0x054c, 0x0010, 0x0106, 0x0450, "Sony", -- cgit v1.2.3 From 7dec0c7a16304b238444cb2c3c8661b6198351e9 Mon Sep 17 00:00:00 2001 From: Phil Dibowitz Date: Sun, 17 Oct 2004 23:55:59 -0700 Subject: [PATCH] USB Storage: unusual_devs patch for winward music disk The following is needed for Winward Music Disk. I narrowed the range of the original patch which was sent by Stephan Walter. From: Stephan Walter Signed-off-by: Phil Dibowitz Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/unusual_devs.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h index 045078d2de5e..a80ef1a7856c 100644 --- a/drivers/usb/storage/unusual_devs.h +++ b/drivers/usb/storage/unusual_devs.h @@ -823,6 +823,14 @@ UNUSUAL_DEV( 0x0dd8, 0x1060, 0x0000, 0xffff, US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_FIX_INQUIRY ), +/* Patch by Stephan Walter + * I don't know why, but it works... */ +UNUSUAL_DEV( 0x0dda, 0x0001, 0x0012, 0x0012, + "WINWARD", + "Music Disk", + US_SC_DEVICE, US_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + /* Submitted by Antoine Mairesse */ UNUSUAL_DEV( 0x0ed1, 0x6660, 0x0100, 0x0300, "USB", -- cgit v1.2.3 From 892716153657e2c535a44ce253129ed44e887a07 Mon Sep 17 00:00:00 2001 From: Phil Dibowitz Date: Sun, 17 Oct 2004 23:56:31 -0700 Subject: [PATCH] USB Storage: Remove unusual_dev entry for IBM Storage Key This removes the 0a16/8888/0100 unusual_devs entry for an IBM USB Storag key. It does not appear to be needed and caused issues for Buddha Henry , who has tested with this patch and his device works properly. Signed-off-by: Phil Dibowitz Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/unusual_devs.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h index a80ef1a7856c..32dd023581ce 100644 --- a/drivers/usb/storage/unusual_devs.h +++ b/drivers/usb/storage/unusual_devs.h @@ -745,12 +745,6 @@ UNUSUAL_DEV( 0x097a, 0x0001, 0x0000, 0x0001, US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_MODE_XLATE ), -UNUSUAL_DEV( 0x0a16, 0x8888, 0x0100, 0x0100, - "IBM", - "IBM USB Memory Key", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_INQUIRY ), - /* This Pentax still camera is not conformant * to the USB storage specification: - * - It does not like the INQUIRY command. So we must handle this command -- cgit v1.2.3 From d6e561b1fae51dfc339e899df7594ccd4375b8a8 Mon Sep 17 00:00:00 2001 From: Phil Dibowitz Date: Sun, 17 Oct 2004 23:56:54 -0700 Subject: [PATCH] USB Storage: Remove unusual_devs entries for Genesys Drives Here's a patch to remove the three Genesys disk entries in unusual_devs. They don't appear to be needed anymore because: 1. The inquiries now request the right amount of data 2. MODE_XLATE, according to Alan, isn't used in 2.6 Signed-off-by: Phil Dibowitz Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/unusual_devs.h | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h index 32dd023581ce..131ed94af904 100644 --- a/drivers/usb/storage/unusual_devs.h +++ b/drivers/usb/storage/unusual_devs.h @@ -460,36 +460,6 @@ UNUSUAL_DEV( 0x05dc, 0xb002, 0x0000, 0x0113, US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_FIX_INQUIRY ), -/* Reported by Carlos Villegas - * This device needs an INQUIRY of exactly 36-bytes to function. - * That is the only reason this entry is needed. - */ -UNUSUAL_DEV( 0x05e3, 0x0700, 0x0000, 0xffff, - "Genesys Logic", - "USB to IDE Card Reader", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_INQUIRY ), - -/* Submitted Alexander Oltu */ -UNUSUAL_DEV( 0x05e3, 0x0701, 0x0000, 0xffff, - "Genesys Logic", - "USB to IDE Optical", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_MODE_XLATE ), - -/* Reported by Peter Marks - * Like the SIIG unit above, this unit needs an INQUIRY to ask for exactly - * 36 bytes of data. No more, no less. That is the only reason this entry - * is needed. - * - * ST818 slim drives (rev 0.02) don't need special care. -*/ -UNUSUAL_DEV( 0x05e3, 0x0702, 0x0000, 0xffff, - "Genesys Logic", - "USB to IDE Disk", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_INQUIRY ), - /* Reported by Hanno Boeck * Taken from the Lycoris Kernel */ UNUSUAL_DEV( 0x0636, 0x0003, 0x0000, 0x9999, -- cgit v1.2.3 From 03a8c871ace48695b7ed940c658a439dc9e32317 Mon Sep 17 00:00:00 2001 From: Phil Dibowitz Date: Sun, 17 Oct 2004 23:57:23 -0700 Subject: [PATCH] USB Storage: Fix Kyocera order This removes a duplicate entry and fixes order. Trivial. Signed-off-by: Phil Dibowitz Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/unusual_devs.h | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h index 131ed94af904..9c12922cd2d9 100644 --- a/drivers/usb/storage/unusual_devs.h +++ b/drivers/usb/storage/unusual_devs.h @@ -88,12 +88,6 @@ UNUSUAL_DEV( 0x0436, 0x0005, 0x0100, 0x0100, US_SC_SCSI, US_PR_DPCM_USB, NULL, 0 ), #endif -/* Patch submitted by Alessandro Fracchetti */ -UNUSUAL_DEV( 0x0482, 0x0105, 0x0100, 0x0100, - "Kyocera", - "Finecam L3", - US_SC_SCSI, US_PR_BULK, NULL, US_FL_FIX_INQUIRY), - /* Patch submitted by Philipp Friedrich */ UNUSUAL_DEV( 0x0482, 0x0100, 0x0100, 0x0100, "Kyocera", @@ -114,6 +108,7 @@ UNUSUAL_DEV( 0x0482, 0x0103, 0x0100, 0x0100, /* Patch for Kyocera Finecam L3 * Submitted by Michael Krauth + * and Alessandro Fracchetti */ UNUSUAL_DEV( 0x0482, 0x0105, 0x0100, 0x0100, "Kyocera", -- cgit v1.2.3 From 35ddffa9df10e78d2f34932b3ce68a6009c650ba Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Sun, 17 Oct 2004 23:57:50 -0700 Subject: [PATCH] USB Storage: new unusual_devs entry Here's another USB mass storage device that incorrectly reports the total number of disk blocks. Please apply. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/unusual_devs.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h index 9c12922cd2d9..e4cfa620b39d 100644 --- a/drivers/usb/storage/unusual_devs.h +++ b/drivers/usb/storage/unusual_devs.h @@ -540,6 +540,13 @@ UNUSUAL_DEV( 0x07ab, 0xfc01, 0x0000, 0x9999, US_SC_QIC, US_PR_FREECOM, freecom_init, 0), #endif +/* Reported by Eero Volotinen */ +UNUSUAL_DEV( 0x07ab, 0xfccd, 0x0406, 0x0406, + "Freecom Technologies", + "FHD-Classic", + US_SC_DEVICE, US_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY), + UNUSUAL_DEV( 0x07af, 0x0004, 0x0100, 0x0133, "Microtech", "USB-SCSI-DB25", -- cgit v1.2.3 From 0bc22f26591c4265fed2d7754a5f328f8d54e11f Mon Sep 17 00:00:00 2001 From: Phil Dibowitz Date: Sun, 17 Oct 2004 23:58:20 -0700 Subject: [PATCH] USB Storage: unusual_dev modification The following patch changes the 0x059f/0xa601/0x0200 per the report from Torsten Eriksson. It adds comments to the uncommented entry and changes the subclass. Signed-off-by: Phil Dibowitz Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/unusual_devs.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h index e4cfa620b39d..1e0eb58f0c09 100644 --- a/drivers/usb/storage/unusual_devs.h +++ b/drivers/usb/storage/unusual_devs.h @@ -399,10 +399,17 @@ UNUSUAL_DEV( 0x0595, 0x4343, 0x0000, 0x2210, "Digital Camera EX-20 DSC", US_SC_8070, US_PR_DEVICE, NULL, 0 ), +/* The entry was here before I took over, and had US_SC_RBC. It turns + * out that isn't needed. Additionally, Torsten Eriksson + * is able to use his device fine + * without this entry at all - but I don't suspect that will be true + * for all users (the protocol is likely needed), so is staying at + * this time. - Phil Dibowitz + */ UNUSUAL_DEV( 0x059f, 0xa601, 0x0200, 0x0200, "LaCie", "USB Hard Disk", - US_SC_RBC, US_PR_CB, NULL, 0 ), + US_SC_DEVICE, US_PR_CB, NULL, 0 ), /* Submitted by Joel Bourquard * Some versions of this device need the SubClass and Protocol overrides -- cgit v1.2.3 From f5d29b1b8e91c8f7dfb97fd682eb817a55f2ccd1 Mon Sep 17 00:00:00 2001 From: Alex Kanavin Date: Mon, 18 Oct 2004 00:41:41 -0700 Subject: [PATCH] USB: USB CDC OBEX driver Also, as the full patch isn't going in, can you please apply this tiny part of it? Somehow the header descriptor was omitted from the CDC ACM driver, but it's present on my phone (thus giving me the "ignoring extra header" error when it's plugged in) and in the CDC spec (section 5.2.3.1). Signed-off-by: Alex Kanavin Signed-Off-By: Oliver Neukum Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-acm.c | 4 +++- drivers/usb/class/cdc-acm.h | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index e302a6cdaa11..0d0d49923bee 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -569,6 +569,8 @@ static int acm_probe (struct usb_interface *intf, break; case CDC_COUNTRY_TYPE: /* maybe somehow export */ break; /* for now we ignore it */ + case CDC_HEADER_TYPE: /* maybe check version */ + break; /* for now we ignore it */ case CDC_AC_MANAGEMENT_TYPE: ac_management_function = buffer[3]; break; @@ -580,7 +582,7 @@ static int acm_probe (struct usb_interface *intf, break; default: - err("Ignoring extra header"); + err("Ignoring extra header, type %d, length %d", buffer[2], buffer[0]); break; } next_desc: diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h index cc26d9517398..da945b367a85 100644 --- a/drivers/usb/class/cdc-acm.h +++ b/drivers/usb/class/cdc-acm.h @@ -117,6 +117,7 @@ struct union_desc { } __attribute__ ((packed)); /* class specific descriptor types */ +#define CDC_HEADER_TYPE 0x00 #define CDC_CALL_MANAGEMENT_TYPE 0x01 #define CDC_AC_MANAGEMENT_TYPE 0x02 #define CDC_UNION_TYPE 0x06 -- cgit v1.2.3 From 7df90674c6656e18d30a90681f3d0bffa9ce084b Mon Sep 17 00:00:00 2001 From: Heinz-Juergen Oertel Date: Mon, 18 Oct 2004 00:42:08 -0700 Subject: [PATCH] USB: usb/serial RM vendor/product id for ftdi_sio Hello, this is a small patch of the USB ftdi_sio driver against linux-2.6.8.1. I only added a new vendor and product id for the RM-CANview, a CAN fieldbus interface: http://www.rmcan.com/site/en/products/gateways/usb/index.htm Thanks Heinz Signed-off-by: Heinz-Juergen Oertel Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/ftdi_sio.c | 3 +++ drivers/usb/serial/ftdi_sio.h | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index 9d3a8e831326..b1a985470e42 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -368,6 +368,7 @@ static struct usb_device_id id_table_8U232AM [] = { { USB_DEVICE_VER(INTREPID_VID, INTREPID_NEOVI_PID, 0, 0x3ff) }, { USB_DEVICE_VER(FALCOM_VID, FALCOM_TWIST_PID, 0, 0x3ff) }, { USB_DEVICE_VER(FTDI_VID, FTDI_SUUNTO_SPORTS_PID, 0, 0x3ff) }, + { USB_DEVICE_VER(FTDI_RM_VID, FTDI_RMCANVIEW_PID, 0, 0x3ff) }, { USB_DEVICE_VER(BANDB_VID, BANDB_USOTL4_PID, 0, 0x3ff) }, { USB_DEVICE_VER(BANDB_VID, BANDB_USTL4_PID, 0, 0x3ff) }, { USB_DEVICE_VER(BANDB_VID, BANDB_USO9ML2_PID, 0, 0x3ff) }, @@ -481,6 +482,7 @@ static struct usb_device_id id_table_FT232BM [] = { { USB_DEVICE_VER(INTREPID_VID, INTREPID_NEOVI_PID, 0x400, 0xffff) }, { USB_DEVICE_VER(FALCOM_VID, FALCOM_TWIST_PID, 0x400, 0xffff) }, { USB_DEVICE_VER(FTDI_VID, FTDI_SUUNTO_SPORTS_PID, 0x400, 0xffff) }, + { USB_DEVICE_VER(FTDI_RM_VID, FTDI_RMCANVIEW_PID, 0x400, 0xffff) }, { USB_DEVICE_VER(BANDB_VID, BANDB_USOTL4_PID, 0x400, 0xffff) }, { USB_DEVICE_VER(BANDB_VID, BANDB_USTL4_PID, 0x400, 0xffff) }, { USB_DEVICE_VER(BANDB_VID, BANDB_USO9ML2_PID, 0x400, 0xffff) }, @@ -601,6 +603,7 @@ static struct usb_device_id id_table_combined [] = { { USB_DEVICE(INTREPID_VID, INTREPID_NEOVI_PID) }, { USB_DEVICE(FALCOM_VID, FALCOM_TWIST_PID) }, { USB_DEVICE(FTDI_VID, FTDI_SUUNTO_SPORTS_PID) }, + { USB_DEVICE(FTDI_RM_VID, FTDI_RMCANVIEW_PID) }, { USB_DEVICE(BANDB_VID, BANDB_USOTL4_PID) }, { USB_DEVICE(BANDB_VID, BANDB_USTL4_PID) }, { USB_DEVICE(BANDB_VID, BANDB_USO9ML2_PID) }, diff --git a/drivers/usb/serial/ftdi_sio.h b/drivers/usb/serial/ftdi_sio.h index bc2b6d5aacaa..e7a7e0aba4f2 100644 --- a/drivers/usb/serial/ftdi_sio.h +++ b/drivers/usb/serial/ftdi_sio.h @@ -233,6 +233,13 @@ #define BANDB_USTL4_PID 0xAC02 /* USTL4 RS-485 Converter */ #define BANDB_USO9ML2_PID 0xAC03 /* USO9ML2 Isolated RS-232 Converter */ +/* + * RM Michaelides CANview USB (http://www.rmcan.com) + * CAN filedbus interface adapter, addad by port GmbH www.port.de) + */ +#define FTDI_RM_VID 0x0403 /* Vendor Id */ +#define FTDI_RMCANVIEW_PID 0xfd60 /* Product Id */ + /* Commands */ #define FTDI_SIO_RESET 0 /* Reset the port */ #define FTDI_SIO_MODEM_CTRL 1 /* Set the modem control register */ -- cgit v1.2.3 From 5a78b22e03bd403db51b4ac905b08c4c1faba37b Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Mon, 18 Oct 2004 00:51:13 -0700 Subject: [PATCH] USB: remove calls to usb_unlink_urb in net/usbnet.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/net/usbnet.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/net/usbnet.c b/drivers/usb/net/usbnet.c index d88503a4e698..ad65e741fa44 100644 --- a/drivers/usb/net/usbnet.c +++ b/drivers/usb/net/usbnet.c @@ -825,7 +825,7 @@ static void ax8817x_unbind(struct usbnet *dev, struct usb_interface *intf) { struct ax8817x_data *data = (struct ax8817x_data *)dev->data; - usb_unlink_urb(data->int_urb); + usb_kill_urb(data->int_urb); usb_free_urb(data->int_urb); kfree(data->int_buf); } @@ -1437,7 +1437,7 @@ static int genelink_free (struct usbnet *dev) // handling needs to be generic) // cancel irq urb first - usb_unlink_urb (priv->irq_urb); + usb_kill_urb (priv->irq_urb); // free irq urb usb_free_urb (priv->irq_urb); -- cgit v1.2.3 From 5b667dd09ac9080ccefd5f12efc6e8cda30d47c0 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Mon, 18 Oct 2004 00:51:42 -0700 Subject: [PATCH] USB: remove calls to usb_unlink_urb() in net/pegasus.c Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/net/pegasus.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/usb/net/pegasus.c b/drivers/usb/net/pegasus.c index c515916e37a7..98cb5ae3f6f1 100644 --- a/drivers/usb/net/pegasus.c +++ b/drivers/usb/net/pegasus.c @@ -854,10 +854,10 @@ static void free_all_urbs(pegasus_t * pegasus) static void unlink_all_urbs(pegasus_t * pegasus) { - usb_unlink_urb(pegasus->intr_urb); - usb_unlink_urb(pegasus->tx_urb); - usb_unlink_urb(pegasus->rx_urb); - usb_unlink_urb(pegasus->ctrl_urb); + usb_kill_urb(pegasus->intr_urb); + usb_kill_urb(pegasus->tx_urb); + usb_kill_urb(pegasus->rx_urb); + usb_kill_urb(pegasus->ctrl_urb); } static int alloc_urbs(pegasus_t * pegasus) @@ -920,8 +920,8 @@ static int pegasus_open(struct net_device *net) if ((res = enable_net_traffic(net, pegasus->usb))) { err("can't enable_net_traffic() - %d", res); res = -EIO; - usb_unlink_urb(pegasus->rx_urb); - usb_unlink_urb(pegasus->intr_urb); + usb_kill_urb(pegasus->rx_urb); + usb_kill_urb(pegasus->intr_urb); free_skb_pool(pegasus); goto exit; } -- cgit v1.2.3 From b94e7ae2aefaec5d5b26016164993340df4f3a71 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Mon, 18 Oct 2004 00:52:07 -0700 Subject: [PATCH] USB: remove calls to usb_unlink_urb() in net/kaweth.c Hi there Greg, here's another one. Signed-off-by: Borislav Petkov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/net/kaweth.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/usb/net/kaweth.c b/drivers/usb/net/kaweth.c index 044d19ece304..2092cb9cb33f 100644 --- a/drivers/usb/net/kaweth.c +++ b/drivers/usb/net/kaweth.c @@ -674,7 +674,7 @@ static int kaweth_open(struct net_device *net) res = usb_submit_urb(kaweth->irq_urb, GFP_KERNEL); if (res) { - usb_unlink_urb(kaweth->rx_urb); + usb_kill_urb(kaweth->rx_urb); return -EIO; } @@ -695,15 +695,15 @@ static int kaweth_close(struct net_device *net) kaweth->status |= KAWETH_STATUS_CLOSING; - usb_unlink_urb(kaweth->irq_urb); - usb_unlink_urb(kaweth->rx_urb); + usb_kill_urb(kaweth->irq_urb); + usb_kill_urb(kaweth->rx_urb); flush_scheduled_work(); /* a scheduled work may have resubmitted, we hit them again */ - usb_unlink_urb(kaweth->irq_urb); - usb_unlink_urb(kaweth->rx_urb); + usb_kill_urb(kaweth->irq_urb); + usb_kill_urb(kaweth->rx_urb); kaweth->status &= ~KAWETH_STATUS_CLOSING; @@ -1173,8 +1173,8 @@ static void kaweth_disconnect(struct usb_interface *intf) } kaweth->removed = 1; - usb_unlink_urb(kaweth->irq_urb); - usb_unlink_urb(kaweth->rx_urb); + usb_kill_urb(kaweth->irq_urb); + usb_kill_urb(kaweth->rx_urb); /* we need to wait for the urb to be cancelled, if it is active */ spin_lock(&kaweth->device_lock); @@ -1260,7 +1260,7 @@ static int usb_start_wait_urb(struct urb *urb, int timeout, int* actual_length) if (!timeout) { // timeout kaweth_warn("usb_control/bulk_msg: timeout"); - usb_unlink_urb(urb); // remove urb safely + usb_kill_urb(urb); // remove urb safely status = -ETIMEDOUT; } else { -- cgit v1.2.3 From 0c802d2940e8e4ca9757718d754ac4178e999262 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Mon, 18 Oct 2004 03:07:29 -0700 Subject: USB: add bulk_in_size for usb-serial devices. Don't know why I didn't do it before... Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/usb-serial.c | 1 + drivers/usb/serial/usb-serial.h | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index ef701224edb6..e3b7c1dd2f4c 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -1047,6 +1047,7 @@ int usb_serial_probe(struct usb_interface *interface, goto probe_error; } buffer_size = endpoint->wMaxPacketSize; + port->bulk_in_size = buffer_size; port->bulk_in_endpointAddress = endpoint->bEndpointAddress; port->bulk_in_buffer = kmalloc (buffer_size, GFP_KERNEL); if (!port->bulk_in_buffer) { diff --git a/drivers/usb/serial/usb-serial.h b/drivers/usb/serial/usb-serial.h index e2e59560d7fb..d3702957b609 100644 --- a/drivers/usb/serial/usb-serial.h +++ b/drivers/usb/serial/usb-serial.h @@ -100,6 +100,7 @@ struct usb_serial_port { __u8 interrupt_in_endpointAddress; unsigned char * bulk_in_buffer; + int bulk_in_size; struct urb * read_urb; __u8 bulk_in_endpointAddress; -- cgit v1.2.3 From 836f98dfaac1b8101f9dbefcfeaa6224f58c8e42 Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Mon, 18 Oct 2004 17:54:35 +0100 Subject: NTFS: Update Documentation/filesystems/ntfs.txt with instructions on how to use the Device-Mapper driver with NTFS ftdisk/LDM raid. This removes the linear raid problem with the Software RAID / MD driver when one or more of the devices has an odd number of sectors. Signed-off-by: Anton Altaparmakov --- Documentation/filesystems/ntfs.txt | 173 +++++++++++++++++++++++++++++++++++-- fs/ntfs/ChangeLog | 4 + 2 files changed, 169 insertions(+), 8 deletions(-) diff --git a/Documentation/filesystems/ntfs.txt b/Documentation/filesystems/ntfs.txt index 970e54f04e89..ad2e32227476 100644 --- a/Documentation/filesystems/ntfs.txt +++ b/Documentation/filesystems/ntfs.txt @@ -10,8 +10,10 @@ Table of contents - Features - Supported mount options - Known bugs and (mis-)features -- Using Software RAID with NTFS -- Limitiations when using the MD driver +- Using NTFS volume and stripe sets + - The Device-Mapper driver + - The Software RAID / MD driver + - Limitiations when using the MD driver - ChangeLog @@ -199,11 +201,161 @@ Please send bug reports/comments/feedback/abuse to the Linux-NTFS development list at sourceforge: linux-ntfs-dev@lists.sourceforge.net -Using Software RAID with NTFS -============================= +Using NTFS volume and stripe sets +================================= + +For support of volume and stripe sets, you can either use the kernel's +Device-Mapper driver or the kernel's Software RAID / MD driver. The former is +the recommended one to use for linear raid. But the latter is required for +raid level 5. For striping and mirroring, either driver should work fine. + + +The Device-Mapper driver +------------------------ + +You will need to create a table of the components of the volume/stripe set and +how they fit together and load this into the kernel using the dmsetup utility +(see man 8 dmsetup). + +Linear volume sets, i.e. linear raid, has been tested and works fine. Even +though untested, there is no reason why stripe sets, i.e. raid level 0, and +mirrors, i.e. raid level 1 should not work, too. Stripes with parity, i.e. +raid level 5, unfortunately cannot work yet because the current version of the +Device-Mapper driver does not support raid level 5. You may be able to use the +Software RAID / MD driver for raid level 5, see the next section for details. + +To create the table describing your volume you will need to know each of its +components and their sizes in sectors, i.e. multiples of 512-byte blocks. + +For NT4 fault tolerant volumes you can obtain the sizes using fdisk. So for +example if one of your partitions is /dev/hda2 you would do: + +$ fdisk -ul /dev/hda + +Disk /dev/hda: 81.9 GB, 81964302336 bytes +255 heads, 63 sectors/track, 9964 cylinders, total 160086528 sectors +Units = sectors of 1 * 512 = 512 bytes + + Device Boot Start End Blocks Id System + /dev/hda1 * 63 4209029 2104483+ 83 Linux + /dev/hda2 4209030 37768814 16779892+ 86 NTFS + /dev/hda3 37768815 46170809 4200997+ 83 Linux + +And you would know that /dev/hda2 has a size of 37768814 - 4209030 + 1 = +33559785 sectors. + +For Win2k and later dynamic disks, you can for example use the ldminfo utility +which is part of the Linux LDM tools (the latest version at the time of +writing is linux-ldm-0.0.8.tar.bz2). You can download it from: + http://linux-ntfs.sourceforge.net/downloads.html +Simply extract the downloaded archive (tar xvjf linux-ldm-0.0.8.tar.bz2), go +into it (cd linux-ldm-0.0.8) and change to the test directory (cd test). You +will find the precompiled (i386) ldminfo utility there. NOTE: You will not be +able to compile this yourself easily so use the binary version! + +Then you would use ldminfo in dump mode to obtain the necessary information: + +$ ./ldminfo --dump /dev/hda + +This would dump the LDM database found on /dev/hda which describes all of your +dinamic disks and all the volumes on them. At the bottom you will see the +VOLUME DEFINITIONS section which is all you really need. You may need to look +further above to determine which of the disks in the volume definitions is +which device in Linux. Hint: Run ldminfo on each of your dinamic disks and +look at the Disk Id close to the top of the output for each (the PRIVATE HEADER +section). You can then find these Disk Ids in the VBLK DATABASE section in the + components where you will get the LDM Name for the disk that is found in +the VOLUME DEFINITIONS section. + +Note you will also need to enable the LDM driver in the Linux kernel. If your +distribution did not enable it, you will need to recompile the kernel with it +enabled. This will create the LDM partitions on each device at boot time. You +would then use those devices (for /dev/hda they would be /dev/hda1, 2, 3, etc) +in the Device-Mapper table. + +You can also bypass using the LDM driver by using the main device (e.g. +/dev/hda) and then using the offsets of the LDM partitions into this device as +the "Start sector of device" when creating the table. Once again ldminfo would +give you the correct information to do this. + +Assuming you know all your devices and their sizes things are easy. + +For a linear raid the table would look like this (note all values are in +512-byte sectors): + +--- cut here --- +# Offset into Size of this Raid type Device Start sector +# volume device of device +0 1028161 linear /dev/hda1 0 +1028161 3903762 linear /dev/hdb2 0 +4931923 2103211 linear /dev/hdc1 0 +--- cut here --- -For support of volume and stripe sets, use the kernel's Software RAID / MD -driver and set up your /etc/raidtab appropriately (see man 5 raidtab). +For a striped volume, i.e. raid level 0, you will need to know the chunk size +you used when creating the volume. Windows uses 64kiB as the default, so it +will probably be this unless you changes the defaults when creating the array. + +For a raid level 0 the table would look like this (note all values are in +512-byte sectors): + +--- cut here --- +# Offset Size Raid Number Chunk 1st Start 2nd Start +# into of the type of size Device in Device in +# volume volume stripes device device +0 2056320 striped 2 128 /dev/hda1 0 /dev/hdb1 0 +--- cut here --- + +If there are more than two devices, just add each of them to the end of the +line. + +Finally, for a mirrored volume, i.e. raid level 1, the table would look like +this (note all values are in 512-byte sectors): + +--- cut here --- +# Ofs Size Raid Log Number Region Should Number Source Start Taget Start +# in of the type type of log size sync? of Device in Device in +# vol volume params mirrors Device Device +0 2056320 mirror core 2 16 nosync 2 /dev/hda1 0 /dev/hdb1 0 +--- cut here --- + +If you are mirroring to multiple devices you can specify further targets at the +end of the line. + +Note the "Should sync?" parameter "nosync" means that the two mirrors are +already in sync which will be the case on a clean shutdown of Windows. If the +mirrors are not clean, you can specify the "sync" option instead of "nosync" +and the Device-Mapper driver will then copy the entirey of the "Source Device" +to the "Target Device" or if you specified multipled target devices to all of +them. + +Once you have your table, save it in a file somewhere (e.g. /etc/ntfsvolume1), +and hand it over to dmsetup to work with, like so: + +$ dmsetup create myvolume1 /etc/ntfsvolume1 + +You can obviously replace "myvolume1" with whatever name you like. + +If it all worked, you will now have the device /dev/device-mapper/myvolume1 +which you can then just use as an argument to the mount command as usual to +mount the ntfs volume. For example: + +$ mount -t ntfs -o ro /dev/device-mapper/myvolume1 /mnt/myvol1 + +(You need to create the directory /mnt/myvol1 first and of course you can use +anything you like instead of /mnt/myvol1 as long as it is an existing +directory.) + +It is advisable to do the mount read-only to see if the volume has been setup +correctly to avoid the possibility of causing damage to the data on the ntfs +volume. + + +The Software RAID / MD driver +----------------------------- + +An alternative to using the Device-Mapper driver is to use the kernel's +Software RAID / MD driver. For which you need to set up your /etc/raidtab +appropriately (see man 5 raidtab). Linear volume sets, i.e. linear raid, as well as stripe sets, i.e. raid level 0, have been tested and work fine (though see section "Limitiations when using @@ -258,8 +410,8 @@ setup correctly to avoid the possibility of causing damage to the data on the ntfs volume. -Limitiations when using the MD driver -===================================== +Limitiations when using the Software RAID / MD driver +----------------------------------------------------- Using the md driver will not work properly if any of your NTFS partitions have an odd number of sectors. This is especially important for linear raid as all @@ -271,6 +423,9 @@ apparent when you try to use the volume again under Windows. So when using linear raid, make sure that all your partitions have an even number of sectors BEFORE attempting to use it. You have been warned! +Even better is to simply use the Device-Mapper for linear raid and then you do +not have this problem with odd numbers of sectors. + ChangeLog ========= @@ -281,6 +436,8 @@ Note, a technical ChangeLog aimed at kernel hackers is in fs/ntfs/ChangeLog. - Fix several race conditions and various other bugs. - Many internal cleanups, code reorganization, optimizations, and mft and index record writing code rewritten to fit in with the changes. + - Update Documentation/filesystems/ntfs.txt with instructions on how to + use the Device-Mapper driver with NTFS ftdisk/LDM raid. 2.1.20: - Fix two stupid bugs introduced in 2.1.18 release. 2.1.19: diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index d526921af13e..b42f7bc9592d 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -138,6 +138,10 @@ ToDo/Notes: record sequence number if it is specified (i.e. not zero). - Add fs/ntfs/mft.[hc]::ntfs_mft_record_alloc() and various helper functions used by it. + - Update Documentation/filesystems/ntfs.txt with instructions on how to + use the Device-Mapper driver with NTFS ftdisk/LDM raid. This removes + the linear raid problem with the Software RAID / MD driver when one + or more of the devices has an odd number of sectors. 2.1.20 - Fix two stupid bugs introduced in 2.1.18 release. -- cgit v1.2.3 From c6fcf5a9eadb0aed33c07b4e829ca9c6614f3807 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Mon, 18 Oct 2004 17:14:46 -0700 Subject: USB: add serial ipw driver Based on a 2.4 tty usb driver from Roelf Diedericks Cleaned up and ported to 2.6 and the usb-serial layer by me. Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/Kconfig | 10 + drivers/usb/serial/Makefile | 1 + drivers/usb/serial/ipw.c | 496 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 507 insertions(+) create mode 100644 drivers/usb/serial/ipw.c diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig index 88fede7b447e..1499bf2fcd13 100644 --- a/drivers/usb/serial/Kconfig +++ b/drivers/usb/serial/Kconfig @@ -187,6 +187,16 @@ config USB_SERIAL_EDGEPORT_TI To compile this driver as a module, choose M here: the module will be called io_ti. +config USB_SERIAL_IPW + tristate "USB IPWireless (3G UMTS TDD) Driver (EXPERIMENTAL)" + depends on USB_SERIAL && EXPERIMENTAL + help + Say Y here if you want to use a IPWireless USB modem such as + the ones supplied by Axity3G/Sentech South Africa. + + To compile this driver as a module, choose M here: the + module will be called ipw. + config USB_SERIAL_KEYSPAN_PDA tristate "USB Keyspan PDA Single Port Serial Driver" depends on USB_SERIAL diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile index 6cfbe3108ccb..dfd43ec80ff3 100644 --- a/drivers/usb/serial/Makefile +++ b/drivers/usb/serial/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_USB_SERIAL_EDGEPORT_TI) += io_ti.o obj-$(CONFIG_USB_SERIAL_EMPEG) += empeg.o obj-$(CONFIG_USB_SERIAL_FTDI_SIO) += ftdi_sio.o obj-$(CONFIG_USB_SERIAL_IPAQ) += ipaq.o +obj-$(CONFIG_USB_SERIAL_IPW) += ipw.o obj-$(CONFIG_USB_SERIAL_IR) += ir-usb.o obj-$(CONFIG_USB_SERIAL_KEYSPAN) += keyspan.o obj-$(CONFIG_USB_SERIAL_KEYSPAN_PDA) += keyspan_pda.o diff --git a/drivers/usb/serial/ipw.c b/drivers/usb/serial/ipw.c new file mode 100644 index 000000000000..2fc04f905a5c --- /dev/null +++ b/drivers/usb/serial/ipw.c @@ -0,0 +1,496 @@ +/* + * IPWireless 3G UMTS TDD Modem driver (USB connected) + * + * Copyright (C) 2004 Roelf Diedericks + * Copyright (C) 2004 Greg Kroah-Hartman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * All information about the device was acquired using SnoopyPro + * on MSFT's O/S, and examing the MSFT drivers' debug output + * (insanely left _on_ in the enduser version) + * + * It was written out of frustration with the IPWireless USB modem + * supplied by Axity3G/Sentech South Africa not supporting + * Linux whatsoever. + * + * Nobody provided any proprietary information that was not already + * available for this device. + * + * The modem adheres to the "3GPP TS 27.007 AT command set for 3G + * User Equipment (UE)" standard, available from + * http://www.3gpp.org/ftp/Specs/html-info/27007.htm + * + * The code was only tested the IPWireless handheld modem distributed + * in South Africa by Sentech. + * + * It may work for Woosh Inc in .nz too, as it appears they use the + * same kit. + * + * There is still some work to be done in terms of handling + * DCD, DTR, RTS, CTS which are currently faked. + * It's good enough for PPP at this point. It's based off all kinds of + * code found in usb/serial and usb/class + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "usb-serial.h" + +/* + * Version Information + */ +#define DRIVER_VERSION "v0.3" +#define DRIVER_AUTHOR "Roelf Diedericks" +#define DRIVER_DESC "IPWireless tty driver" + +#define IPW_TTY_MAJOR 240 /* real device node major id, experimental range */ +#define IPW_TTY_MINORS 256 /* we support 256 devices, dunno why, it'd be insane :) */ + +#define USB_IPW_MAGIC 0x6d02 /* magic number for ipw struct */ + + +/* Message sizes */ +#define EVENT_BUFFER_SIZE 0xFF +#define CHAR2INT16(c1,c0) (((u32)((c1) & 0xff) << 8) + (u32)((c0) & 0xff)) +#define NUM_BULK_URBS 24 +#define NUM_CONTROL_URBS 16 + +/* vendor/product pairs that are known work with this driver*/ +#define IPW_VID 0x0bc3 +#define IPW_PID 0x0001 + + +/* Vendor commands: */ + +/* baud rates */ +enum { + ipw_sio_b256000 = 0x000e, + ipw_sio_b128000 = 0x001d, + ipw_sio_b115200 = 0x0020, + ipw_sio_b57600 = 0x0040, + ipw_sio_b56000 = 0x0042, + ipw_sio_b38400 = 0x0060, + ipw_sio_b19200 = 0x00c0, + ipw_sio_b14400 = 0x0100, + ipw_sio_b9600 = 0x0180, + ipw_sio_b4800 = 0x0300, + ipw_sio_b2400 = 0x0600, + ipw_sio_b1200 = 0x0c00, + ipw_sio_b600 = 0x1800 +}; + +/* data bits */ +#define ipw_dtb_7 0x700 +#define ipw_dtb_8 0x810 // ok so the define is misleading, I know, but forces 8,n,1 + // I mean, is there a point to any other setting these days? :) + +/* usb control request types : */ +#define IPW_SIO_RXCTL 0x00 // control bulk rx channel transmissions, value=1/0 (on/off) +#define IPW_SIO_SET_BAUD 0x01 // set baud, value=requested ipw_sio_bxxxx +#define IPW_SIO_SET_LINE 0x03 // set databits, parity. value=ipw_dtb_x +#define IPW_SIO_SET_PIN 0x03 // set/clear dtr/rts value=ipw_pin_xxx +#define IPW_SIO_POLL 0x08 // get serial port status byte, call with value=0 +#define IPW_SIO_INIT 0x11 // initializes ? value=0 (appears as first thing todo on open) +#define IPW_SIO_PURGE 0x12 // purge all transmissions?, call with value=numchar_to_purge +#define IPW_SIO_HANDFLOW 0x13 // set xon/xoff limits value=0, and a buffer of 0x10 bytes +#define IPW_SIO_SETCHARS 0x13 // set the flowcontrol special chars, value=0, buf=6 bytes, + // last 2 bytes contain flowcontrol chars e.g. 00 00 00 00 11 13 + +/* values used for request IPW_SIO_SET_PIN */ +#define IPW_PIN_SETDTR 0x101 +#define IPW_PIN_SETRTS 0x202 +#define IPW_PIN_CLRDTR 0x100 +#define IPW_PIN_CLRRTS 0x200 // unconfirmed + +/* values used for request IPW_SIO_RXCTL */ +#define IPW_RXBULK_ON 1 +#define IPW_RXBULK_OFF 0 + +/* various 16 byte hardcoded transferbuffers used by flow control */ +#define IPW_BYTES_FLOWINIT { 0x01, 0, 0, 0, 0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } + +/* Interpretation of modem status lines */ +/* These need sorting out by individually connecting pins and checking + * results. FIXME! + * When data is being sent we see 0x30 in the lower byte; this must + * contain DSR and CTS ... + */ +#define IPW_DSR ((1<<4) | (1<<5)) +#define IPW_CTS ((1<<5) | (1<<4)) + +#define IPW_WANTS_TO_SEND 0x30 +//#define IPW_DTR /* Data Terminal Ready */ +//#define IPW_CTS /* Clear To Send */ +//#define IPW_CD /* Carrier Detect */ +//#define IPW_DSR /* Data Set Ready */ +//#define IPW_RxD /* Receive pin */ + +//#define IPW_LE +//#define IPW_RTS +//#define IPW_ST +//#define IPW_SR +//#define IPW_RI /* Ring Indicator */ + +static struct usb_device_id usb_ipw_ids[] = { + { USB_DEVICE(IPW_VID, IPW_PID) }, + { }, +}; + +MODULE_DEVICE_TABLE(usb, usb_ipw_ids); + +static struct usb_driver usb_ipw_driver = { + .owner = THIS_MODULE, + .name = "ipwtty", + .probe = usb_serial_probe, + .disconnect = usb_serial_disconnect, + .id_table = usb_ipw_ids, +}; + +static int debug; + +static void ipw_read_bulk_callback(struct urb *urb, struct pt_regs *regs) +{ + struct usb_serial_port *port = urb->context; + unsigned char *data = urb->transfer_buffer; + struct tty_struct *tty; + int i; + int result; + + dbg("%s - port %d", __FUNCTION__, port->number); + + if (urb->status) { + dbg("%s - nonzero read bulk status received: %d", __FUNCTION__, urb->status); + return; + } + + usb_serial_debug_data(debug, &port->dev, __FUNCTION__, urb->actual_length, data); + + tty = port->tty; + if (tty && urb->actual_length) { + for (i = 0; i < urb->actual_length ; ++i) { + /* if we insert more than TTY_FLIPBUF_SIZE characters, we drop them. */ + if(tty->flip.count >= TTY_FLIPBUF_SIZE) { + tty_flip_buffer_push(tty); + } + /* this doesn't actually push the data through unless tty->low_latency is set */ + tty_insert_flip_char(tty, data[i], 0); + } + tty_flip_buffer_push(tty); + } + + /* Continue trying to always read */ + usb_fill_bulk_urb (port->read_urb, port->serial->dev, + usb_rcvbulkpipe(port->serial->dev, + port->bulk_in_endpointAddress), + port->read_urb->transfer_buffer, + port->read_urb->transfer_buffer_length, + ipw_read_bulk_callback, port); + result = usb_submit_urb(port->read_urb, GFP_ATOMIC); + if (result) + dev_err(&port->dev, "%s - failed resubmitting read urb, error %d\n", __FUNCTION__, result); + return; +} + +static int ipw_open(struct usb_serial_port *port, struct file *filp) +{ + struct usb_device *dev = port->serial->dev; + u8 buf_flow_static[16] = IPW_BYTES_FLOWINIT; + u8 *buf_flow_init; + int result; + + dbg("%s", __FUNCTION__); + + buf_flow_init = kmalloc(16, GFP_KERNEL); + if (!buf_flow_init) + return -ENOMEM; + memcpy(buf_flow_init, buf_flow_static, 16); + + if (port->tty) + port->tty->low_latency = 1; + + /* --1: Tell the modem to initialize (we think) From sniffs this is always the + * first thing that gets sent to the modem during opening of the device */ + dbg("%s: Sending SIO_INIT (we guess)",__FUNCTION__); + result = usb_control_msg(dev, usb_sndctrlpipe(dev,0), + IPW_SIO_INIT, + USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT, + 0, + 0, /* index */ + NULL, + 0, + 100*HZ); + if (result < 0) + dev_err(&port->dev, "Init of modem failed (error = %d)", result); + + /* reset the bulk pipes */ + usb_clear_halt(dev, usb_rcvbulkpipe(dev, port->bulk_in_endpointAddress)); + usb_clear_halt(dev, usb_sndbulkpipe(dev, port->bulk_out_endpointAddress)); + + /*--2: Start reading from the device */ + dbg("%s: setting up bulk read callback",__FUNCTION__); + usb_fill_bulk_urb(port->read_urb, dev, + usb_rcvbulkpipe(dev, port->bulk_in_endpointAddress), + port->bulk_in_buffer, + port->bulk_in_size, + ipw_read_bulk_callback, port); + result = usb_submit_urb(port->read_urb, GFP_KERNEL); + if (result < 0) + dbg("%s - usb_submit_urb(read bulk) failed with status %d", __FUNCTION__, result); + + /*--3: Tell the modem to open the floodgates on the rx bulk channel */ + dbg("%s:asking modem for RxRead (RXBULK_ON)",__FUNCTION__); + result = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + IPW_SIO_RXCTL, + USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT, + IPW_RXBULK_ON, + 0, /* index */ + NULL, + 0, + 100*HZ); + if (result < 0) + dev_err(&port->dev, "Enabling bulk RxRead failed (error = %d)", result); + + /*--4: setup the initial flowcontrol */ + dbg("%s:setting init flowcontrol (%s)",__FUNCTION__,buf_flow_init); + result = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + IPW_SIO_HANDFLOW, + USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT, + 0, + 0, + buf_flow_init, + 0x10, + 200*HZ); + if (result < 0) + dev_err(&port->dev, "initial flowcontrol failed (error = %d)", result); + + + /*--5: raise the dtr */ + dbg("%s:raising dtr",__FUNCTION__); + result = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + IPW_SIO_SET_PIN, + USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT, + IPW_PIN_SETDTR, + 0, + NULL, + 0, + 200*HZ); + if (result < 0) + dev_err(&port->dev, "setting dtr failed (error = %d)", result); + + /*--6: raise the rts */ + dbg("%s:raising rts",__FUNCTION__); + result = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + IPW_SIO_SET_PIN, + USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT, + IPW_PIN_SETRTS, + 0, + NULL, + 0, + 200*HZ); + if (result < 0) + dev_err(&port->dev, "setting dtr failed (error = %d)", result); + + kfree(buf_flow_init); + return 0; +} + +static void ipw_close(struct usb_serial_port *port, struct file * filp) +{ + struct usb_device *dev = port->serial->dev; + int result; + + if (tty_hung_up_p(filp)) { + dbg("%s: tty_hung_up_p ...", __FUNCTION__); + return; + } + + /*--1: drop the dtr */ + dbg("%s:dropping dtr",__FUNCTION__); + result = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + IPW_SIO_SET_PIN, + USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT, + IPW_PIN_CLRDTR, + 0, + NULL, + 0, + 200*HZ); + if (result < 0) + dev_err(&port->dev, "dropping dtr failed (error = %d)", result); + + /*--2: drop the rts */ + dbg("%s:dropping rts",__FUNCTION__); + result = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + IPW_SIO_SET_PIN, USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT, + IPW_PIN_CLRRTS, + 0, + NULL, + 0, + 200*HZ); + if (result < 0) + dev_err(&port->dev, "dropping rts failed (error = %d)", result); + + + /*--3: purge */ + dbg("%s:sending purge",__FUNCTION__); + result = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + IPW_SIO_PURGE, USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT, + 0x03, + 0, + NULL, + 0, + 200*HZ); + if (result < 0) + dev_err(&port->dev, "purge failed (error = %d)", result); + + + /* send RXBULK_off (tell modem to stop transmitting bulk data on rx chan) */ + result = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + IPW_SIO_RXCTL, + USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT, + IPW_RXBULK_OFF, + 0, /* index */ + NULL, + 0, + 100*HZ); + + if (result < 0) + dev_err(&port->dev, "Disabling bulk RxRead failed (error = %d)", result); + + /* shutdown any in-flight urbs that we know about */ + usb_kill_urb(port->read_urb); + usb_kill_urb(port->write_urb); +} + +static void ipw_write_bulk_callback(struct urb *urb, struct pt_regs *regs) +{ + struct usb_serial_port *port = urb->context; + + dbg("%s", __FUNCTION__); + + if (urb->status) + dbg("%s - nonzero write bulk status received: %d", __FUNCTION__, urb->status); + + schedule_work(&port->work); +} + +static int ipw_write(struct usb_serial_port *port, int from_user, const unsigned char *buf, int count) +{ + struct usb_device *dev = port->serial->dev; + int ret; + + dbg("%s: TOP: count=%d, from_user=%d, in_interrupt=%ld", __FUNCTION__, + count, from_user, in_interrupt() ); + + if (count == 0) { + dbg("%s - write request of 0 bytes", __FUNCTION__); + return 0; + } + + /* Racy and broken, FIXME properly! */ + if (port->write_urb->status == -EINPROGRESS) + return 0; + + count = min(count, port->bulk_out_size); + if (from_user) { + if (copy_from_user(port->bulk_out_buffer, buf, count)) + return -EFAULT; + } else { + memcpy(port->bulk_out_buffer, buf, count); + } + + dbg("%s count now:%d", __FUNCTION__, count); + + usb_fill_bulk_urb(port->write_urb, dev, + usb_sndbulkpipe(dev, port->bulk_out_endpointAddress), + port->write_urb->transfer_buffer, + count, + ipw_write_bulk_callback, + port); + + ret = usb_submit_urb(port->write_urb, GFP_ATOMIC); + if (ret != 0) { + dbg("%s - usb_submit_urb(write bulk) failed with error = %d", __FUNCTION__, ret); + return ret; + } + + dbg("%s returning %d", __FUNCTION__, count); + return count; +} + +static int ipw_probe(struct usb_serial_port *port) +{ + return 0; +} + +static int ipw_disconnect(struct usb_serial_port *port) +{ + usb_set_serial_port_data(port, NULL); + return 0; +} + +static struct usb_serial_device_type ipw_device = { + .owner = THIS_MODULE, + .name = "IPWireless converter", + .short_name = "ipw", + .id_table = usb_ipw_ids, + .num_interrupt_in = NUM_DONT_CARE, + .num_bulk_in = 1, + .num_bulk_out = 1, + .num_ports = 1, + .open = ipw_open, + .close = ipw_close, + .port_probe = ipw_probe, + .port_remove = ipw_disconnect, + .write = ipw_write, + .write_bulk_callback = ipw_write_bulk_callback, + .read_bulk_callback = ipw_read_bulk_callback, +}; + + + +int usb_ipw_init(void) +{ + int retval; + + retval = usb_serial_register(&ipw_device); + if (retval) + return retval; + retval = usb_register(&usb_ipw_driver); + if (retval) { + usb_serial_deregister(&ipw_device); + return retval; + } + info(DRIVER_DESC " " DRIVER_VERSION); + return 0; +} + +void usb_ipw_exit(void) +{ + usb_deregister(&usb_ipw_driver); + usb_serial_deregister(&ipw_device); +} + +module_init(usb_ipw_init); +module_exit(usb_ipw_exit); + +/* Module information */ +MODULE_AUTHOR( DRIVER_AUTHOR ); +MODULE_DESCRIPTION( DRIVER_DESC ); +MODULE_LICENSE("GPL"); + +module_param(debug, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug enabled or not"); -- cgit v1.2.3 From ed594d2d7d644a80402e4da598035538c454a3cf Mon Sep 17 00:00:00 2001 From: Peter Osterlund Date: Mon, 18 Oct 2004 17:57:09 -0700 Subject: [PATCH] DVD+RW support This patch adds support for using DVD+RW drives as writable block devices. The patch is based on work from: Andy Polyakov - Wrote the 2.4 patch Nigel Kukard - Initial porting to 2.6.x It works for me using an Iomega Super DVD 8x USB drive. Nov 5 2001, Aug 8 2002. Modified by Andy Polyakov to support MMC-3 complaint DVD+RW units. Modified by Nigel Kukard - support DVD+RW 2.4.x patch by Andy Polyakov This patch implements CDRW packet writing as a kernel block device. Usage instructions are in the packet-writing.txt file. A hint: If you don't want to wait for a complete disc format, you can format just a part of the disc. For example: cdrwtool -d /dev/hdc -m 10240 This will format 10240 blocks, ie 20MB. Signed-off-by: Peter Osterlund Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/cdrom/cdrom.c | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++ drivers/ide/ide-cd.c | 2 ++ drivers/scsi/sr.c | 1 + include/linux/cdrom.h | 2 ++ 4 files changed, 85 insertions(+) diff --git a/drivers/cdrom/cdrom.c b/drivers/cdrom/cdrom.c index e57d19031f8e..d815b8a5b1e8 100644 --- a/drivers/cdrom/cdrom.c +++ b/drivers/cdrom/cdrom.c @@ -848,6 +848,41 @@ static int cdrom_ram_open_write(struct cdrom_device_info *cdi) return ret; } +static void cdrom_mmc3_profile(struct cdrom_device_info *cdi) +{ + struct packet_command cgc; + char buffer[32]; + int ret, mmc3_profile; + + init_cdrom_command(&cgc, buffer, sizeof(buffer), CGC_DATA_READ); + + cgc.cmd[0] = GPCMD_GET_CONFIGURATION; + cgc.cmd[1] = 0; + cgc.cmd[2] = cgc.cmd[3] = 0; /* Starting Feature Number */ + cgc.cmd[7] = 0; cgc.cmd [8] = 8; /* Allocation Length */ + cgc.quiet = 1; + + if ((ret = cdi->ops->generic_packet(cdi, &cgc))) { + mmc3_profile = 0xffff; + } else { + mmc3_profile = (buffer[6] << 8) | buffer[7]; + printk(KERN_INFO "cdrom: %s: mmc-3 profile capable, current profile: %Xh\n", + cdi->name, mmc3_profile); + } + cdi->mmc3_profile = mmc3_profile; +} + +static int cdrom_is_dvd_rw(struct cdrom_device_info *cdi) +{ + switch (cdi->mmc3_profile) { + case 0x12: /* DVD-RAM */ + case 0x1A: /* DVD+RW */ + return 0; + default: + return 1; + } +} + /* * returns 0 for ok to open write, non-0 to disallow */ @@ -889,10 +924,50 @@ static int cdrom_open_write(struct cdrom_device_info *cdi) ret = cdrom_ram_open_write(cdi); else if (CDROM_CAN(CDC_MO_DRIVE)) ret = mo_open_write(cdi); + else if (!cdrom_is_dvd_rw(cdi)) + ret = 0; return ret; } +static void cdrom_dvd_rw_close_write(struct cdrom_device_info *cdi) +{ + struct packet_command cgc; + + if (cdi->mmc3_profile != 0x1a) { + cdinfo(CD_CLOSE, "%s: No DVD+RW\n", cdi->name); + return; + } + + if (!cdi->media_written) { + cdinfo(CD_CLOSE, "%s: DVD+RW media clean\n", cdi->name); + return; + } + + printk(KERN_INFO "cdrom: %s: dirty DVD+RW media, \"finalizing\"\n", + cdi->name); + + init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE); + cgc.cmd[0] = GPCMD_FLUSH_CACHE; + cgc.timeout = 30*HZ; + cdi->ops->generic_packet(cdi, &cgc); + + init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE); + cgc.cmd[0] = GPCMD_CLOSE_TRACK; + cgc.timeout = 3000*HZ; + cgc.quiet = 1; + cdi->ops->generic_packet(cdi, &cgc); + + init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE); + cgc.cmd[0] = GPCMD_CLOSE_TRACK; + cgc.cmd[2] = 2; /* Close session */ + cgc.quiet = 1; + cgc.timeout = 3000*HZ; + cdi->ops->generic_packet(cdi, &cgc); + + cdi->media_written = 0; +} + static int cdrom_close_write(struct cdrom_device_info *cdi) { #if 0 @@ -925,6 +1000,7 @@ int cdrom_open(struct cdrom_device_info *cdi, struct inode *ip, struct file *fp) ret = open_for_data(cdi); if (ret) goto err; + cdrom_mmc3_profile(cdi); if (fp->f_mode & FMODE_WRITE) { ret = -EROFS; if (cdrom_open_write(cdi)) @@ -932,6 +1008,7 @@ int cdrom_open(struct cdrom_device_info *cdi, struct inode *ip, struct file *fp) if (!CDROM_CAN(CDC_RAM)) goto err; ret = 0; + cdi->media_written = 0; } } @@ -1123,6 +1200,8 @@ int cdrom_release(struct cdrom_device_info *cdi, struct file *fp) cdi->use_count--; if (cdi->use_count == 0) cdinfo(CD_CLOSE, "Use count for \"/dev/%s\" now zero\n", cdi->name); + if (cdi->use_count == 0) + cdrom_dvd_rw_close_write(cdi); if (cdi->use_count == 0 && (cdo->capability & CDC_LOCK) && !keeplocked) { cdinfo(CD_CLOSE, "Unlocking door!\n"); @@ -1329,6 +1408,7 @@ int media_changed(struct cdrom_device_info *cdi, int queue) if (cdi->ops->media_changed(cdi, CDSL_CURRENT)) { cdi->mc_flags = 0x3; /* set bit on both queues */ ret |= 1; + cdi->media_written = 0; } cdi->mc_flags &= ~mask; /* clear bit */ return ret; diff --git a/drivers/ide/ide-cd.c b/drivers/ide/ide-cd.c index cc6b66e34e37..468eec62246f 100644 --- a/drivers/ide/ide-cd.c +++ b/drivers/ide/ide-cd.c @@ -1932,6 +1932,8 @@ static ide_startstop_t cdrom_start_write(ide_drive_t *drive, struct request *rq) info->dma = drive->using_dma ? 1 : 0; info->cmd = WRITE; + info->devinfo.media_written = 1; + /* Start sending the write request to the drive. */ return cdrom_start_packet_command(drive, 32768, cdrom_start_write_cont); } diff --git a/drivers/scsi/sr.c b/drivers/scsi/sr.c index b6c76b5680a2..20a10a3ed5e4 100644 --- a/drivers/scsi/sr.c +++ b/drivers/scsi/sr.c @@ -377,6 +377,7 @@ static int sr_init_command(struct scsi_cmnd * SCpnt) return 0; SCpnt->cmnd[0] = WRITE_10; SCpnt->sc_data_direction = DMA_TO_DEVICE; + cd->cdi.media_written = 1; } else if (rq_data_dir(SCpnt->request) == READ) { SCpnt->cmnd[0] = READ_10; SCpnt->sc_data_direction = DMA_FROM_DEVICE; diff --git a/include/linux/cdrom.h b/include/linux/cdrom.h index bcc9410761d9..73a1ade6a8e9 100644 --- a/include/linux/cdrom.h +++ b/include/linux/cdrom.h @@ -947,6 +947,8 @@ struct cdrom_device_info { __u8 reserved : 6; /* not used yet */ int cdda_method; /* see flags */ __u8 last_sense; + __u8 media_written; /* dirty flag, DVD+RW bookkeeping */ + unsigned short mmc3_profile; /* current MMC3 profile */ int for_data; int (*exit)(struct cdrom_device_info *); int mrw_mode_page; -- cgit v1.2.3 From a7cbd7da4fb59371ed56373caca648daeb2f1d55 Mon Sep 17 00:00:00 2001 From: Peter Osterlund Date: Mon, 18 Oct 2004 17:57:21 -0700 Subject: [PATCH] packet-writing: add credits Nigel pointed out that the earlier patches contained attributions that are not present in this patch. The 2.4 patch contains: Nov 5 2001, Aug 8 2002. Modified by Andy Polyakov to support MMC-3 complaint DVD+RW units. and Nigel changed it to this in his 2.6 patch: Modified by Nigel Kukard - support DVD+RW 2.4.x patch by Andy Polyakov The patch I sent you deleted most of the earlier work and moved the rest to cdrom.c, but the comments were not moved over, since the earlier authors didn't modify cdrom.c. Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/cdrom/cdrom.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/cdrom/cdrom.c b/drivers/cdrom/cdrom.c index d815b8a5b1e8..3da410b2c940 100644 --- a/drivers/cdrom/cdrom.c +++ b/drivers/cdrom/cdrom.c @@ -234,6 +234,12 @@ -- Mt Rainier support -- DVD-RAM write open fixes + Nov 5 2001, Aug 8 2002. Modified by Andy Polyakov + to support MMC-3 compliant DVD+RW units. + + Modified by Nigel Kukard - support DVD+RW + 2.4.x patch by Andy Polyakov + -------------------------------------------------------------------------*/ #define REVISION "Revision: 3.20" -- cgit v1.2.3 From 2f8e2dc86c9876edca632e8ef2ab1f68d1b753f0 Mon Sep 17 00:00:00 2001 From: Peter Osterlund Date: Mon, 18 Oct 2004 17:57:34 -0700 Subject: [PATCH] CDRW packet writing support This patch implements CDRW packet writing as a kernel block device. Usage instructions are in the packet-writing.txt file. A hint: If you don't want to wait for a complete disc format, you can format just a part of the disc. For example: cdrwtool -d /dev/hdc -m 10240 This will format 10240 blocks, ie 20MB. Signed-off-by: Peter Osterlund Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- Documentation/cdrom/00-INDEX | 2 + Documentation/cdrom/packet-writing.txt | 86 + drivers/block/Kconfig | 33 + drivers/block/Makefile | 1 + drivers/block/pktcdvd.c | 2679 ++++++++++++++++++++++++++++++++ drivers/cdrom/Makefile | 1 + drivers/ide/ide-cd.c | 6 +- drivers/scsi/sr.c | 6 +- fs/compat_ioctl.c | 1 + include/linux/cdrom.h | 1 + include/linux/compat_ioctl.h | 2 + include/linux/pktcdvd.h | 275 ++++ 12 files changed, 3088 insertions(+), 5 deletions(-) create mode 100644 Documentation/cdrom/packet-writing.txt create mode 100644 drivers/block/pktcdvd.c create mode 100644 include/linux/pktcdvd.h diff --git a/Documentation/cdrom/00-INDEX b/Documentation/cdrom/00-INDEX index eae6896676f2..916dafe29d3f 100644 --- a/Documentation/cdrom/00-INDEX +++ b/Documentation/cdrom/00-INDEX @@ -22,6 +22,8 @@ mcdx - info on improved Mitsumi CD-ROM driver. optcd - info on the Optics Storage 8000 AT CD-ROM driver +packet-writing.txt + - Info on the CDRW packet writing module sbpcd - info on the SoundBlaster/Panasonic CD-ROM interface driver. sjcd diff --git a/Documentation/cdrom/packet-writing.txt b/Documentation/cdrom/packet-writing.txt new file mode 100644 index 000000000000..d34fcbca9f27 --- /dev/null +++ b/Documentation/cdrom/packet-writing.txt @@ -0,0 +1,86 @@ +Getting started quick +--------------------- + +- Select packet support in the block device section and UDF support in + the file system section. + +- Compile and install kernel and modules, reboot. + +- You need the udftools package (pktsetup, mkudffs, cdrwtool). + Download from http://sourceforge.net/projects/linux-udf/ + +- Grab a new CD-RW disc and format it (assuming CD-RW is hdc, substitute + as appropriate): + # cdrwtool -d /dev/hdc -q + +- Setup your writer + # pktsetup dev_name /dev/hdc + +- Now you can mount /dev/pktcdvd/dev_name and copy files to it. Enjoy! + # mount /dev/pktcdvd/dev_name /cdrom -t udf -o rw,noatime + + +Packet writing for DVD-RW media +------------------------------- + +DVD-RW discs can be written to much like CD-RW discs if they are in +the so called "restricted overwrite" mode. To put a disc in restricted +overwrite mode, run: + + # dvd+rw-format /dev/hdc + +You can then use the disc the same way you would use a CD-RW disc: + + # pktsetup dev_name /dev/hdc + # mount /dev/pktcdvd/dev_name /cdrom -t udf -o rw,noatime + + +Packet writing for DVD+RW media +------------------------------- + +According to the DVD+RW specification, a drive supporting DVD+RW discs +shall implement "true random writes with 2KB granularity", which means +that it should be possible to put any filesystem with a block size >= +2KB on such a disc. For example, it should be possible to do: + + # mkudffs /dev/hdc + # mount /dev/hdc /cdrom -t udf -o rw,noatime + +However, some drives don't follow the specification and expect the +host to perform aligned writes at 32KB boundaries. Other drives do +follow the specification, but suffer bad performance problems if the +writes are not 32KB aligned. + +Both problems can be solved by using the pktcdvd driver, which always +generates aligned writes. + + # pktsetup dev_name /dev/hdc + # mkudffs /dev/pktcdvd/dev_name + # mount /dev/pktcdvd/dev_name /cdrom -t udf -o rw,noatime + + +Notes +----- + +- CD-RW media can usually not be overwritten more than about 1000 + times, so to avoid unnecessary wear on the media, you should always + use the noatime mount option. + +- Defect management (ie automatic remapping of bad sectors) has not + been implemented yet, so you are likely to get at least some + filesystem corruption if the disc wears out. + +- Since the pktcdvd driver makes the disc appear as a regular block + device with a 2KB block size, you can put any filesystem you like on + the disc. For example, run: + + # /sbin/mke2fs /dev/pktcdvd/dev_name + + to create an ext2 filesystem on the disc. + + +Links +----- + +See http://fy.chalmers.se/~appro/linux/DVD+RW/ for more information +about DVD writing. diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig index a1d50242b8cd..6a43c807497d 100644 --- a/drivers/block/Kconfig +++ b/drivers/block/Kconfig @@ -356,6 +356,39 @@ config LBD your machine, or if you want to have a raid or loopback device bigger than 2TB. Otherwise say N. +config CDROM_PKTCDVD + tristate "Packet writing on CD/DVD media" + help + If you have a CDROM drive that supports packet writing, say Y to + include preliminary support. It should work with any MMC/Mt Fuji + compliant ATAPI or SCSI drive, which is just about any newer CD + writer. + + Currently only writing to CD-RW, DVD-RW and DVD+RW discs is possible. + DVD-RW disks must be in restricted overwrite mode. + + To compile this driver as a module, choose M here: the + module will be called pktcdvd. + +config CDROM_PKTCDVD_BUFFERS + int "Free buffers for data gathering" + depends on CDROM_PKTCDVD + default "8" + help + This controls the maximum number of active concurrent packets. More + concurrent packets can increase write performance, but also require + more memory. Each concurrent packet will require approximately 64Kb + of non-swappable kernel memory, memory which will be allocated at + pktsetup time. + +config CDROM_PKTCDVD_WCACHE + bool "Enable write caching" + depends on CDROM_PKTCDVD + help + If enabled, write caching will be set for the CD-R/W device. For now + this option is dangerous unless the CD-RW media is known good, as we + don't do deferred write error handling yet. + source "drivers/s390/block/Kconfig" endmenu diff --git a/drivers/block/Makefile b/drivers/block/Makefile index c8fbbf14ce94..1cf09a1c065b 100644 --- a/drivers/block/Makefile +++ b/drivers/block/Makefile @@ -35,6 +35,7 @@ obj-$(CONFIG_BLK_DEV_XD) += xd.o obj-$(CONFIG_BLK_CPQ_DA) += cpqarray.o obj-$(CONFIG_BLK_CPQ_CISS_DA) += cciss.o obj-$(CONFIG_BLK_DEV_DAC960) += DAC960.o +obj-$(CONFIG_CDROM_PKTCDVD) += pktcdvd.o obj-$(CONFIG_BLK_DEV_UMEM) += umem.o obj-$(CONFIG_BLK_DEV_NBD) += nbd.o diff --git a/drivers/block/pktcdvd.c b/drivers/block/pktcdvd.c new file mode 100644 index 000000000000..fb80b6a91f84 --- /dev/null +++ b/drivers/block/pktcdvd.c @@ -0,0 +1,2679 @@ +/* + * Copyright (C) 2000 Jens Axboe + * Copyright (C) 2001-2004 Peter Osterlund + * + * May be copied or modified under the terms of the GNU General Public + * License. See linux/COPYING for more information. + * + * Packet writing layer for ATAPI and SCSI CD-R, CD-RW, DVD-R, and + * DVD-RW devices (aka an exercise in block layer masturbation) + * + * + * TODO: (circa order of when I will fix it) + * - Only able to write on CD-RW media right now. + * - check host application code on media and set it in write page + * - interface for UDF <-> packet to negotiate a new location when a write + * fails. + * - handle OPC, especially for -RW media + * + * Theory of operation: + * + * We use a custom make_request_fn function that forwards reads directly to + * the underlying CD device. Write requests are either attached directly to + * a live packet_data object, or simply stored sequentially in a list for + * later processing by the kcdrwd kernel thread. This driver doesn't use + * any elevator functionally as defined by the elevator_s struct, but the + * underlying CD device uses a standard elevator. + * + * This strategy makes it possible to do very late merging of IO requests. + * A new bio sent to pkt_make_request can be merged with a live packet_data + * object even if the object is in the data gathering state. + * + *************************************************************************/ + +#define VERSION_CODE "v0.2.0a 2004-07-14 Jens Axboe (axboe@suse.de) and petero2@telia.com" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if PACKET_DEBUG +#define DPRINTK(fmt, args...) printk(KERN_NOTICE fmt, ##args) +#else +#define DPRINTK(fmt, args...) +#endif + +#if PACKET_DEBUG > 1 +#define VPRINTK(fmt, args...) printk(KERN_NOTICE fmt, ##args) +#else +#define VPRINTK(fmt, args...) +#endif + +#define MAX_SPEED 0xffff + +#define ZONE(sector, pd) (((sector) + (pd)->offset) & ~((pd)->settings.size - 1)) + +static struct pktcdvd_device *pkt_devs[MAX_WRITERS]; +static struct proc_dir_entry *pkt_proc; +static int pkt_major; +static struct semaphore ctl_mutex; /* Serialize open/close/setup/teardown */ +static mempool_t *psd_pool; + + +static void pkt_bio_finished(struct pktcdvd_device *pd) +{ + BUG_ON(atomic_read(&pd->cdrw.pending_bios) <= 0); + if (atomic_dec_and_test(&pd->cdrw.pending_bios)) { + VPRINTK("pktcdvd: queue empty\n"); + atomic_set(&pd->iosched.attention, 1); + wake_up(&pd->wqueue); + } +} + +static void pkt_bio_destructor(struct bio *bio) +{ + kfree(bio->bi_io_vec); + kfree(bio); +} + +static struct bio *pkt_bio_alloc(int nr_iovecs) +{ + struct bio_vec *bvl = NULL; + struct bio *bio; + + bio = kmalloc(sizeof(struct bio), GFP_KERNEL); + if (!bio) + goto no_bio; + bio_init(bio); + + bvl = kmalloc(nr_iovecs * sizeof(struct bio_vec), GFP_KERNEL); + if (!bvl) + goto no_bvl; + memset(bvl, 0, nr_iovecs * sizeof(struct bio_vec)); + + bio->bi_max_vecs = nr_iovecs; + bio->bi_io_vec = bvl; + bio->bi_destructor = pkt_bio_destructor; + + return bio; + + no_bvl: + kfree(bio); + no_bio: + return NULL; +} + +/* + * Allocate a packet_data struct + */ +static struct packet_data *pkt_alloc_packet_data(void) +{ + int i; + struct packet_data *pkt; + + pkt = kmalloc(sizeof(struct packet_data), GFP_KERNEL); + if (!pkt) + goto no_pkt; + memset(pkt, 0, sizeof(struct packet_data)); + + pkt->w_bio = pkt_bio_alloc(PACKET_MAX_SIZE); + if (!pkt->w_bio) + goto no_bio; + + for (i = 0; i < PAGES_PER_PACKET; i++) { + pkt->pages[i] = alloc_page(GFP_KERNEL); + if (!pkt->pages[i]) + goto no_page; + } + for (i = 0; i < PAGES_PER_PACKET; i++) + clear_page(page_address(pkt->pages[i])); + + spin_lock_init(&pkt->lock); + + for (i = 0; i < PACKET_MAX_SIZE; i++) { + struct bio *bio = pkt_bio_alloc(1); + if (!bio) + goto no_rd_bio; + pkt->r_bios[i] = bio; + } + + return pkt; + +no_rd_bio: + for (i = 0; i < PACKET_MAX_SIZE; i++) { + struct bio *bio = pkt->r_bios[i]; + if (bio) + bio_put(bio); + } + +no_page: + for (i = 0; i < PAGES_PER_PACKET; i++) + if (pkt->pages[i]) + __free_page(pkt->pages[i]); + bio_put(pkt->w_bio); +no_bio: + kfree(pkt); +no_pkt: + return NULL; +} + +/* + * Free a packet_data struct + */ +static void pkt_free_packet_data(struct packet_data *pkt) +{ + int i; + + for (i = 0; i < PACKET_MAX_SIZE; i++) { + struct bio *bio = pkt->r_bios[i]; + if (bio) + bio_put(bio); + } + for (i = 0; i < PAGES_PER_PACKET; i++) + __free_page(pkt->pages[i]); + bio_put(pkt->w_bio); + kfree(pkt); +} + +static void pkt_shrink_pktlist(struct pktcdvd_device *pd) +{ + struct packet_data *pkt, *next; + + BUG_ON(!list_empty(&pd->cdrw.pkt_active_list)); + + list_for_each_entry_safe(pkt, next, &pd->cdrw.pkt_free_list, list) { + pkt_free_packet_data(pkt); + } +} + +static int pkt_grow_pktlist(struct pktcdvd_device *pd, int nr_packets) +{ + struct packet_data *pkt; + + INIT_LIST_HEAD(&pd->cdrw.pkt_free_list); + INIT_LIST_HEAD(&pd->cdrw.pkt_active_list); + spin_lock_init(&pd->cdrw.active_list_lock); + while (nr_packets > 0) { + pkt = pkt_alloc_packet_data(); + if (!pkt) { + pkt_shrink_pktlist(pd); + return 0; + } + pkt->id = nr_packets; + pkt->pd = pd; + list_add(&pkt->list, &pd->cdrw.pkt_free_list); + nr_packets--; + } + return 1; +} + +static void *pkt_rb_alloc(int gfp_mask, void *data) +{ + return kmalloc(sizeof(struct pkt_rb_node), gfp_mask); +} + +static void pkt_rb_free(void *ptr, void *data) +{ + kfree(ptr); +} + +static inline struct pkt_rb_node *pkt_rbtree_next(struct pkt_rb_node *node) +{ + struct rb_node *n = rb_next(&node->rb_node); + if (!n) + return NULL; + return rb_entry(n, struct pkt_rb_node, rb_node); +} + +static inline void pkt_rbtree_erase(struct pktcdvd_device *pd, struct pkt_rb_node *node) +{ + rb_erase(&node->rb_node, &pd->bio_queue); + mempool_free(node, pd->rb_pool); + pd->bio_queue_size--; + BUG_ON(pd->bio_queue_size < 0); +} + +/* + * Find the first node in the pd->bio_queue rb tree with a starting sector >= s. + */ +static struct pkt_rb_node *pkt_rbtree_find(struct pktcdvd_device *pd, sector_t s) +{ + struct rb_node *n = pd->bio_queue.rb_node; + struct rb_node *next; + struct pkt_rb_node *tmp; + + if (!n) { + BUG_ON(pd->bio_queue_size > 0); + return NULL; + } + + for (;;) { + tmp = rb_entry(n, struct pkt_rb_node, rb_node); + if (s <= tmp->bio->bi_sector) + next = n->rb_left; + else + next = n->rb_right; + if (!next) + break; + n = next; + } + + if (s > tmp->bio->bi_sector) { + tmp = pkt_rbtree_next(tmp); + if (!tmp) + return NULL; + } + BUG_ON(s > tmp->bio->bi_sector); + return tmp; +} + +/* + * Insert a node into the pd->bio_queue rb tree. + */ +static void pkt_rbtree_insert(struct pktcdvd_device *pd, struct pkt_rb_node *node) +{ + struct rb_node **p = &pd->bio_queue.rb_node; + struct rb_node *parent = NULL; + sector_t s = node->bio->bi_sector; + struct pkt_rb_node *tmp; + + while (*p) { + parent = *p; + tmp = rb_entry(parent, struct pkt_rb_node, rb_node); + if (s < tmp->bio->bi_sector) + p = &(*p)->rb_left; + else + p = &(*p)->rb_right; + } + rb_link_node(&node->rb_node, parent, p); + rb_insert_color(&node->rb_node, &pd->bio_queue); + pd->bio_queue_size++; +} + +/* + * Add a bio to a single linked list defined by its head and tail pointers. + */ +static inline void pkt_add_list_last(struct bio *bio, struct bio **list_head, struct bio **list_tail) +{ + bio->bi_next = NULL; + if (*list_tail) { + BUG_ON((*list_head) == NULL); + (*list_tail)->bi_next = bio; + (*list_tail) = bio; + } else { + BUG_ON((*list_head) != NULL); + (*list_head) = bio; + (*list_tail) = bio; + } +} + +/* + * Remove and return the first bio from a single linked list defined by its + * head and tail pointers. + */ +static inline struct bio *pkt_get_list_first(struct bio **list_head, struct bio **list_tail) +{ + struct bio *bio; + + if (*list_head == NULL) + return NULL; + + bio = *list_head; + *list_head = bio->bi_next; + if (*list_head == NULL) + *list_tail = NULL; + + bio->bi_next = NULL; + return bio; +} + +/* + * Send a packet_command to the underlying block device and + * wait for completion. + */ +static int pkt_generic_packet(struct pktcdvd_device *pd, struct packet_command *cgc) +{ + char sense[SCSI_SENSE_BUFFERSIZE]; + request_queue_t *q; + struct request *rq; + DECLARE_COMPLETION(wait); + int err = 0; + + q = bdev_get_queue(pd->bdev); + + rq = blk_get_request(q, (cgc->data_direction == CGC_DATA_WRITE) ? WRITE : READ, + __GFP_WAIT); + rq->errors = 0; + rq->rq_disk = pd->bdev->bd_disk; + rq->bio = NULL; + rq->buffer = NULL; + rq->timeout = 60*HZ; + rq->data = cgc->buffer; + rq->data_len = cgc->buflen; + rq->sense = sense; + memset(sense, 0, sizeof(sense)); + rq->sense_len = 0; + rq->flags |= REQ_BLOCK_PC | REQ_HARDBARRIER; + if (cgc->quiet) + rq->flags |= REQ_QUIET; + memcpy(rq->cmd, cgc->cmd, CDROM_PACKET_SIZE); + if (sizeof(rq->cmd) > CDROM_PACKET_SIZE) + memset(rq->cmd + CDROM_PACKET_SIZE, 0, sizeof(rq->cmd) - CDROM_PACKET_SIZE); + + rq->ref_count++; + rq->flags |= REQ_NOMERGE; + rq->waiting = &wait; + elv_add_request(q, rq, ELEVATOR_INSERT_BACK, 1); + generic_unplug_device(q); + wait_for_completion(&wait); + + if (rq->errors) + err = -EIO; + + blk_put_request(rq); + return err; +} + +/* + * A generic sense dump / resolve mechanism should be implemented across + * all ATAPI + SCSI devices. + */ +static void pkt_dump_sense(struct packet_command *cgc) +{ + static char *info[9] = { "No sense", "Recovered error", "Not ready", + "Medium error", "Hardware error", "Illegal request", + "Unit attention", "Data protect", "Blank check" }; + int i; + struct request_sense *sense = cgc->sense; + + printk("pktcdvd:"); + for (i = 0; i < CDROM_PACKET_SIZE; i++) + printk(" %02x", cgc->cmd[i]); + printk(" - "); + + if (sense == NULL) { + printk("no sense\n"); + return; + } + + printk("sense %02x.%02x.%02x", sense->sense_key, sense->asc, sense->ascq); + + if (sense->sense_key > 8) { + printk(" (INVALID)\n"); + return; + } + + printk(" (%s)\n", info[sense->sense_key]); +} + +/* + * flush the drive cache to media + */ +static int pkt_flush_cache(struct pktcdvd_device *pd) +{ + struct packet_command cgc; + + init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE); + cgc.cmd[0] = GPCMD_FLUSH_CACHE; + cgc.quiet = 1; + + /* + * the IMMED bit -- we default to not setting it, although that + * would allow a much faster close, this is safer + */ +#if 0 + cgc.cmd[1] = 1 << 1; +#endif + return pkt_generic_packet(pd, &cgc); +} + +/* + * speed is given as the normal factor, e.g. 4 for 4x + */ +static int pkt_set_speed(struct pktcdvd_device *pd, unsigned write_speed, unsigned read_speed) +{ + struct packet_command cgc; + struct request_sense sense; + int ret; + + init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE); + cgc.sense = &sense; + cgc.cmd[0] = GPCMD_SET_SPEED; + cgc.cmd[2] = (read_speed >> 8) & 0xff; + cgc.cmd[3] = read_speed & 0xff; + cgc.cmd[4] = (write_speed >> 8) & 0xff; + cgc.cmd[5] = write_speed & 0xff; + + if ((ret = pkt_generic_packet(pd, &cgc))) + pkt_dump_sense(&cgc); + + return ret; +} + +/* + * Queue a bio for processing by the low-level CD device. Must be called + * from process context. + */ +static void pkt_queue_bio(struct pktcdvd_device *pd, struct bio *bio, int high_prio_read) +{ + spin_lock(&pd->iosched.lock); + if (bio_data_dir(bio) == READ) { + pkt_add_list_last(bio, &pd->iosched.read_queue, + &pd->iosched.read_queue_tail); + if (high_prio_read) + pd->iosched.high_prio_read = 1; + } else { + pkt_add_list_last(bio, &pd->iosched.write_queue, + &pd->iosched.write_queue_tail); + } + spin_unlock(&pd->iosched.lock); + + atomic_set(&pd->iosched.attention, 1); + wake_up(&pd->wqueue); +} + +/* + * Process the queued read/write requests. This function handles special + * requirements for CDRW drives: + * - A cache flush command must be inserted before a read request if the + * previous request was a write. + * - Switching between reading and writing is slow, so don't it more often + * than necessary. + * - Set the read speed according to current usage pattern. When only reading + * from the device, it's best to use the highest possible read speed, but + * when switching often between reading and writing, it's better to have the + * same read and write speeds. + * - Reads originating from user space should have higher priority than reads + * originating from pkt_gather_data, because some process is usually waiting + * on reads of the first kind. + */ +static void pkt_iosched_process_queue(struct pktcdvd_device *pd) +{ + request_queue_t *q; + + if (atomic_read(&pd->iosched.attention) == 0) + return; + atomic_set(&pd->iosched.attention, 0); + + q = bdev_get_queue(pd->bdev); + + for (;;) { + struct bio *bio; + int reads_queued, writes_queued, high_prio_read; + + spin_lock(&pd->iosched.lock); + reads_queued = (pd->iosched.read_queue != NULL); + writes_queued = (pd->iosched.write_queue != NULL); + if (!reads_queued) + pd->iosched.high_prio_read = 0; + high_prio_read = pd->iosched.high_prio_read; + spin_unlock(&pd->iosched.lock); + + if (!reads_queued && !writes_queued) + break; + + if (pd->iosched.writing) { + if (high_prio_read || (!writes_queued && reads_queued)) { + if (atomic_read(&pd->cdrw.pending_bios) > 0) { + VPRINTK("pktcdvd: write, waiting\n"); + break; + } + pkt_flush_cache(pd); + pd->iosched.writing = 0; + } + } else { + if (!reads_queued && writes_queued) { + if (atomic_read(&pd->cdrw.pending_bios) > 0) { + VPRINTK("pktcdvd: read, waiting\n"); + break; + } + pd->iosched.writing = 1; + } + } + + spin_lock(&pd->iosched.lock); + if (pd->iosched.writing) { + bio = pkt_get_list_first(&pd->iosched.write_queue, + &pd->iosched.write_queue_tail); + } else { + bio = pkt_get_list_first(&pd->iosched.read_queue, + &pd->iosched.read_queue_tail); + } + spin_unlock(&pd->iosched.lock); + + if (!bio) + continue; + + if (bio_data_dir(bio) == READ) + pd->iosched.successive_reads += bio->bi_size >> 10; + else + pd->iosched.successive_reads = 0; + if (pd->iosched.successive_reads >= HI_SPEED_SWITCH) { + if (pd->read_speed == pd->write_speed) { + pd->read_speed = MAX_SPEED; + pkt_set_speed(pd, pd->write_speed, pd->read_speed); + } + } else { + if (pd->read_speed != pd->write_speed) { + pd->read_speed = pd->write_speed; + pkt_set_speed(pd, pd->write_speed, pd->read_speed); + } + } + + atomic_inc(&pd->cdrw.pending_bios); + generic_make_request(bio); + } +} + +/* + * Special care is needed if the underlying block device has a small + * max_phys_segments value. + */ +static int pkt_set_segment_merging(struct pktcdvd_device *pd, request_queue_t *q) +{ + if ((pd->settings.size << 9) / CD_FRAMESIZE <= q->max_phys_segments) { + /* + * The cdrom device can handle one segment/frame + */ + clear_bit(PACKET_MERGE_SEGS, &pd->flags); + return 0; + } else if ((pd->settings.size << 9) / PAGE_SIZE <= q->max_phys_segments) { + /* + * We can handle this case at the expense of some extra memory + * copies during write operations + */ + set_bit(PACKET_MERGE_SEGS, &pd->flags); + return 0; + } else { + printk("pktcdvd: cdrom max_phys_segments too small\n"); + return -EIO; + } +} + +/* + * Copy CD_FRAMESIZE bytes from src_bio into a destination page + */ +static void pkt_copy_bio_data(struct bio *src_bio, int seg, int offs, + struct page *dst_page, int dst_offs) +{ + unsigned int copy_size = CD_FRAMESIZE; + + while (copy_size > 0) { + struct bio_vec *src_bvl = bio_iovec_idx(src_bio, seg); + void *vfrom = kmap_atomic(src_bvl->bv_page, KM_USER0) + + src_bvl->bv_offset + offs; + void *vto = page_address(dst_page) + dst_offs; + int len = min_t(int, copy_size, src_bvl->bv_len - offs); + + BUG_ON(len < 0); + memcpy(vto, vfrom, len); + kunmap_atomic(src_bvl->bv_page, KM_USER0); + + seg++; + offs = 0; + dst_offs += len; + copy_size -= len; + } +} + +/* + * Copy all data for this packet to pkt->pages[], so that + * a) The number of required segments for the write bio is minimized, which + * is necessary for some scsi controllers. + * b) The data can be used as cache to avoid read requests if we receive a + * new write request for the same zone. + */ +static void pkt_make_local_copy(struct packet_data *pkt, struct page **pages, int *offsets) +{ + int f, p, offs; + + /* Copy all data to pkt->pages[] */ + p = 0; + offs = 0; + for (f = 0; f < pkt->frames; f++) { + if (pages[f] != pkt->pages[p]) { + void *vfrom = kmap_atomic(pages[f], KM_USER0) + offsets[f]; + void *vto = page_address(pkt->pages[p]) + offs; + memcpy(vto, vfrom, CD_FRAMESIZE); + kunmap_atomic(pages[f], KM_USER0); + pages[f] = pkt->pages[p]; + offsets[f] = offs; + } else { + BUG_ON(offsets[f] != offs); + } + offs += CD_FRAMESIZE; + if (offs >= PAGE_SIZE) { + BUG_ON(offs > PAGE_SIZE); + offs = 0; + p++; + } + } +} + +static int pkt_end_io_read(struct bio *bio, unsigned int bytes_done, int err) +{ + struct packet_data *pkt = bio->bi_private; + struct pktcdvd_device *pd = pkt->pd; + BUG_ON(!pd); + + if (bio->bi_size) + return 1; + + VPRINTK("pkt_end_io_read: bio=%p sec0=%llx sec=%llx err=%d\n", bio, + (unsigned long long)pkt->sector, (unsigned long long)bio->bi_sector, err); + + if (err) + atomic_inc(&pkt->io_errors); + if (atomic_dec_and_test(&pkt->io_wait)) { + atomic_inc(&pkt->run_sm); + wake_up(&pd->wqueue); + } + pkt_bio_finished(pd); + + return 0; +} + +static int pkt_end_io_packet_write(struct bio *bio, unsigned int bytes_done, int err) +{ + struct packet_data *pkt = bio->bi_private; + struct pktcdvd_device *pd = pkt->pd; + BUG_ON(!pd); + + if (bio->bi_size) + return 1; + + VPRINTK("pkt_end_io_packet_write: id=%d, err=%d\n", pkt->id, err); + + pd->stats.pkt_ended++; + + pkt_bio_finished(pd); + atomic_dec(&pkt->io_wait); + atomic_inc(&pkt->run_sm); + wake_up(&pd->wqueue); + return 0; +} + +/* + * Schedule reads for the holes in a packet + */ +static void pkt_gather_data(struct pktcdvd_device *pd, struct packet_data *pkt) +{ + int frames_read = 0; + struct bio *bio; + int f; + char written[PACKET_MAX_SIZE]; + + BUG_ON(!pkt->orig_bios); + + atomic_set(&pkt->io_wait, 0); + atomic_set(&pkt->io_errors, 0); + + if (pkt->cache_valid) { + VPRINTK("pkt_gather_data: zone %llx cached\n", + (unsigned long long)pkt->sector); + goto out_account; + } + + /* + * Figure out which frames we need to read before we can write. + */ + memset(written, 0, sizeof(written)); + spin_lock(&pkt->lock); + for (bio = pkt->orig_bios; bio; bio = bio->bi_next) { + int first_frame = (bio->bi_sector - pkt->sector) / (CD_FRAMESIZE >> 9); + int num_frames = bio->bi_size / CD_FRAMESIZE; + BUG_ON(first_frame < 0); + BUG_ON(first_frame + num_frames > pkt->frames); + for (f = first_frame; f < first_frame + num_frames; f++) + written[f] = 1; + } + spin_unlock(&pkt->lock); + + /* + * Schedule reads for missing parts of the packet. + */ + for (f = 0; f < pkt->frames; f++) { + int p, offset; + if (written[f]) + continue; + bio = pkt->r_bios[f]; + bio_init(bio); + bio->bi_max_vecs = 1; + bio->bi_sector = pkt->sector + f * (CD_FRAMESIZE >> 9); + bio->bi_bdev = pd->bdev; + bio->bi_end_io = pkt_end_io_read; + bio->bi_private = pkt; + + p = (f * CD_FRAMESIZE) / PAGE_SIZE; + offset = (f * CD_FRAMESIZE) % PAGE_SIZE; + VPRINTK("pkt_gather_data: Adding frame %d, page:%p offs:%d\n", + f, pkt->pages[p], offset); + if (!bio_add_page(bio, pkt->pages[p], CD_FRAMESIZE, offset)) + BUG(); + + atomic_inc(&pkt->io_wait); + bio->bi_rw = READ; + pkt_queue_bio(pd, bio, 0); + frames_read++; + } + +out_account: + VPRINTK("pkt_gather_data: need %d frames for zone %llx\n", + frames_read, (unsigned long long)pkt->sector); + pd->stats.pkt_started++; + pd->stats.secs_rg += frames_read * (CD_FRAMESIZE >> 9); + pd->stats.secs_w += pd->settings.size; +} + +/* + * Find a packet matching zone, or the least recently used packet if + * there is no match. + */ +static struct packet_data *pkt_get_packet_data(struct pktcdvd_device *pd, int zone) +{ + struct packet_data *pkt; + + list_for_each_entry(pkt, &pd->cdrw.pkt_free_list, list) { + if (pkt->sector == zone || pkt->list.next == &pd->cdrw.pkt_free_list) { + list_del_init(&pkt->list); + if (pkt->sector != zone) + pkt->cache_valid = 0; + break; + } + } + return pkt; +} + +static void pkt_put_packet_data(struct pktcdvd_device *pd, struct packet_data *pkt) +{ + if (pkt->cache_valid) { + list_add(&pkt->list, &pd->cdrw.pkt_free_list); + } else { + list_add_tail(&pkt->list, &pd->cdrw.pkt_free_list); + } +} + +/* + * recover a failed write, query for relocation if possible + * + * returns 1 if recovery is possible, or 0 if not + * + */ +static int pkt_start_recovery(struct packet_data *pkt) +{ + /* + * FIXME. We need help from the file system to implement + * recovery handling. + */ + return 0; +#if 0 + struct request *rq = pkt->rq; + struct pktcdvd_device *pd = rq->rq_disk->private_data; + struct block_device *pkt_bdev; + struct super_block *sb = NULL; + unsigned long old_block, new_block; + sector_t new_sector; + + pkt_bdev = bdget(kdev_t_to_nr(pd->pkt_dev)); + if (pkt_bdev) { + sb = get_super(pkt_bdev); + bdput(pkt_bdev); + } + + if (!sb) + return 0; + + if (!sb->s_op || !sb->s_op->relocate_blocks) + goto out; + + old_block = pkt->sector / (CD_FRAMESIZE >> 9); + if (sb->s_op->relocate_blocks(sb, old_block, &new_block)) + goto out; + + new_sector = new_block * (CD_FRAMESIZE >> 9); + pkt->sector = new_sector; + + pkt->bio->bi_sector = new_sector; + pkt->bio->bi_next = NULL; + pkt->bio->bi_flags = 1 << BIO_UPTODATE; + pkt->bio->bi_idx = 0; + + BUG_ON(pkt->bio->bi_rw != (1 << BIO_RW)); + BUG_ON(pkt->bio->bi_vcnt != pkt->frames); + BUG_ON(pkt->bio->bi_size != pkt->frames * CD_FRAMESIZE); + BUG_ON(pkt->bio->bi_end_io != pkt_end_io_packet_write); + BUG_ON(pkt->bio->bi_private != pkt); + + drop_super(sb); + return 1; + +out: + drop_super(sb); + return 0; +#endif +} + +static inline void pkt_set_state(struct packet_data *pkt, enum packet_data_state state) +{ +#if PACKET_DEBUG > 1 + static const char *state_name[] = { + "IDLE", "WAITING", "READ_WAIT", "WRITE_WAIT", "RECOVERY", "FINISHED" + }; + enum packet_data_state old_state = pkt->state; + VPRINTK("pkt %2d : s=%6llx %s -> %s\n", pkt->id, (unsigned long long)pkt->sector, + state_name[old_state], state_name[state]); +#endif + pkt->state = state; +} + +/* + * Scan the work queue to see if we can start a new packet. + * returns non-zero if any work was done. + */ +static int pkt_handle_queue(struct pktcdvd_device *pd) +{ + struct packet_data *pkt, *p; + struct bio *bio = NULL; + sector_t zone = 0; /* Suppress gcc warning */ + struct pkt_rb_node *node, *first_node; + struct rb_node *n; + + VPRINTK("handle_queue\n"); + + atomic_set(&pd->scan_queue, 0); + + if (list_empty(&pd->cdrw.pkt_free_list)) { + VPRINTK("handle_queue: no pkt\n"); + return 0; + } + + /* + * Try to find a zone we are not already working on. + */ + spin_lock(&pd->lock); + first_node = pkt_rbtree_find(pd, pd->current_sector); + if (!first_node) { + n = rb_first(&pd->bio_queue); + if (n) + first_node = rb_entry(n, struct pkt_rb_node, rb_node); + } + node = first_node; + while (node) { + bio = node->bio; + zone = ZONE(bio->bi_sector, pd); + list_for_each_entry(p, &pd->cdrw.pkt_active_list, list) { + if (p->sector == zone) + goto try_next_bio; + } + break; +try_next_bio: + node = pkt_rbtree_next(node); + if (!node) { + n = rb_first(&pd->bio_queue); + if (n) + node = rb_entry(n, struct pkt_rb_node, rb_node); + } + if (node == first_node) + node = NULL; + } + spin_unlock(&pd->lock); + if (!bio) { + VPRINTK("handle_queue: no bio\n"); + return 0; + } + + pkt = pkt_get_packet_data(pd, zone); + BUG_ON(!pkt); + + pd->current_sector = zone + pd->settings.size; + pkt->sector = zone; + pkt->frames = pd->settings.size >> 2; + BUG_ON(pkt->frames > PACKET_MAX_SIZE); + pkt->write_size = 0; + + /* + * Scan work queue for bios in the same zone and link them + * to this packet. + */ + spin_lock(&pd->lock); + VPRINTK("pkt_handle_queue: looking for zone %llx\n", (unsigned long long)zone); + while ((node = pkt_rbtree_find(pd, zone)) != NULL) { + bio = node->bio; + VPRINTK("pkt_handle_queue: found zone=%llx\n", + (unsigned long long)ZONE(bio->bi_sector, pd)); + if (ZONE(bio->bi_sector, pd) != zone) + break; + pkt_rbtree_erase(pd, node); + spin_lock(&pkt->lock); + pkt_add_list_last(bio, &pkt->orig_bios, &pkt->orig_bios_tail); + pkt->write_size += bio->bi_size / CD_FRAMESIZE; + spin_unlock(&pkt->lock); + } + spin_unlock(&pd->lock); + + pkt->sleep_time = max(PACKET_WAIT_TIME, 1); + pkt_set_state(pkt, PACKET_WAITING_STATE); + atomic_set(&pkt->run_sm, 1); + + spin_lock(&pd->cdrw.active_list_lock); + list_add(&pkt->list, &pd->cdrw.pkt_active_list); + spin_unlock(&pd->cdrw.active_list_lock); + + return 1; +} + +/* + * Assemble a bio to write one packet and queue the bio for processing + * by the underlying block device. + */ +static void pkt_start_write(struct pktcdvd_device *pd, struct packet_data *pkt) +{ + struct bio *bio; + struct page *pages[PACKET_MAX_SIZE]; + int offsets[PACKET_MAX_SIZE]; + int f; + int frames_write; + + for (f = 0; f < pkt->frames; f++) { + pages[f] = pkt->pages[(f * CD_FRAMESIZE) / PAGE_SIZE]; + offsets[f] = (f * CD_FRAMESIZE) % PAGE_SIZE; + } + + /* + * Fill-in pages[] and offsets[] with data from orig_bios. + */ + frames_write = 0; + spin_lock(&pkt->lock); + for (bio = pkt->orig_bios; bio; bio = bio->bi_next) { + int segment = bio->bi_idx; + int src_offs = 0; + int first_frame = (bio->bi_sector - pkt->sector) / (CD_FRAMESIZE >> 9); + int num_frames = bio->bi_size / CD_FRAMESIZE; + BUG_ON(first_frame < 0); + BUG_ON(first_frame + num_frames > pkt->frames); + for (f = first_frame; f < first_frame + num_frames; f++) { + struct bio_vec *src_bvl = bio_iovec_idx(bio, segment); + + while (src_offs >= src_bvl->bv_len) { + src_offs -= src_bvl->bv_len; + segment++; + BUG_ON(segment >= bio->bi_vcnt); + src_bvl = bio_iovec_idx(bio, segment); + } + + if (src_bvl->bv_len - src_offs >= CD_FRAMESIZE) { + pages[f] = src_bvl->bv_page; + offsets[f] = src_bvl->bv_offset + src_offs; + } else { + pkt_copy_bio_data(bio, segment, src_offs, + pages[f], offsets[f]); + } + src_offs += CD_FRAMESIZE; + frames_write++; + } + } + pkt_set_state(pkt, PACKET_WRITE_WAIT_STATE); + spin_unlock(&pkt->lock); + + VPRINTK("pkt_start_write: Writing %d frames for zone %llx\n", + frames_write, (unsigned long long)pkt->sector); + BUG_ON(frames_write != pkt->write_size); + + if (test_bit(PACKET_MERGE_SEGS, &pd->flags) || (pkt->write_size < pkt->frames)) { + pkt_make_local_copy(pkt, pages, offsets); + pkt->cache_valid = 1; + } else { + pkt->cache_valid = 0; + } + + /* Start the write request */ + bio_init(pkt->w_bio); + pkt->w_bio->bi_max_vecs = PACKET_MAX_SIZE; + pkt->w_bio->bi_sector = pkt->sector; + pkt->w_bio->bi_bdev = pd->bdev; + pkt->w_bio->bi_end_io = pkt_end_io_packet_write; + pkt->w_bio->bi_private = pkt; + for (f = 0; f < pkt->frames; f++) { + if ((f + 1 < pkt->frames) && (pages[f + 1] == pages[f]) && + (offsets[f + 1] = offsets[f] + CD_FRAMESIZE)) { + if (!bio_add_page(pkt->w_bio, pages[f], CD_FRAMESIZE * 2, offsets[f])) + BUG(); + f++; + } else { + if (!bio_add_page(pkt->w_bio, pages[f], CD_FRAMESIZE, offsets[f])) + BUG(); + } + } + VPRINTK("pktcdvd: vcnt=%d\n", pkt->w_bio->bi_vcnt); + + atomic_set(&pkt->io_wait, 1); + pkt->w_bio->bi_rw = WRITE; + pkt_queue_bio(pd, pkt->w_bio, 0); +} + +static void pkt_finish_packet(struct packet_data *pkt, int uptodate) +{ + struct bio *bio, *next; + + if (!uptodate) + pkt->cache_valid = 0; + + /* Finish all bios corresponding to this packet */ + bio = pkt->orig_bios; + while (bio) { + next = bio->bi_next; + bio->bi_next = NULL; + bio_endio(bio, bio->bi_size, uptodate ? 0 : -EIO); + bio = next; + } + pkt->orig_bios = pkt->orig_bios_tail = NULL; +} + +static void pkt_run_state_machine(struct pktcdvd_device *pd, struct packet_data *pkt) +{ + int uptodate; + + VPRINTK("run_state_machine: pkt %d\n", pkt->id); + + for (;;) { + switch (pkt->state) { + case PACKET_WAITING_STATE: + if ((pkt->write_size < pkt->frames) && (pkt->sleep_time > 0)) + return; + + pkt->sleep_time = 0; + pkt_gather_data(pd, pkt); + pkt_set_state(pkt, PACKET_READ_WAIT_STATE); + break; + + case PACKET_READ_WAIT_STATE: + if (atomic_read(&pkt->io_wait) > 0) + return; + + if (atomic_read(&pkt->io_errors) > 0) { + pkt_set_state(pkt, PACKET_RECOVERY_STATE); + } else { + pkt_start_write(pd, pkt); + } + break; + + case PACKET_WRITE_WAIT_STATE: + if (atomic_read(&pkt->io_wait) > 0) + return; + + if (test_bit(BIO_UPTODATE, &pkt->w_bio->bi_flags)) { + pkt_set_state(pkt, PACKET_FINISHED_STATE); + } else { + pkt_set_state(pkt, PACKET_RECOVERY_STATE); + } + break; + + case PACKET_RECOVERY_STATE: + if (pkt_start_recovery(pkt)) { + pkt_start_write(pd, pkt); + } else { + VPRINTK("No recovery possible\n"); + pkt_set_state(pkt, PACKET_FINISHED_STATE); + } + break; + + case PACKET_FINISHED_STATE: + uptodate = test_bit(BIO_UPTODATE, &pkt->w_bio->bi_flags); + pkt_finish_packet(pkt, uptodate); + return; + + default: + BUG(); + break; + } + } +} + +static void pkt_handle_packets(struct pktcdvd_device *pd) +{ + struct packet_data *pkt, *next; + + VPRINTK("pkt_handle_packets\n"); + + /* + * Run state machine for active packets + */ + list_for_each_entry(pkt, &pd->cdrw.pkt_active_list, list) { + if (atomic_read(&pkt->run_sm) > 0) { + atomic_set(&pkt->run_sm, 0); + pkt_run_state_machine(pd, pkt); + } + } + + /* + * Move no longer active packets to the free list + */ + spin_lock(&pd->cdrw.active_list_lock); + list_for_each_entry_safe(pkt, next, &pd->cdrw.pkt_active_list, list) { + if (pkt->state == PACKET_FINISHED_STATE) { + list_del(&pkt->list); + pkt_put_packet_data(pd, pkt); + pkt_set_state(pkt, PACKET_IDLE_STATE); + atomic_set(&pd->scan_queue, 1); + } + } + spin_unlock(&pd->cdrw.active_list_lock); +} + +static void pkt_count_states(struct pktcdvd_device *pd, int *states) +{ + struct packet_data *pkt; + int i; + + for (i = 0; i <= PACKET_NUM_STATES; i++) + states[i] = 0; + + spin_lock(&pd->cdrw.active_list_lock); + list_for_each_entry(pkt, &pd->cdrw.pkt_active_list, list) { + states[pkt->state]++; + } + spin_unlock(&pd->cdrw.active_list_lock); +} + +/* + * kcdrwd is woken up when writes have been queued for one of our + * registered devices + */ +static int kcdrwd(void *foobar) +{ + struct pktcdvd_device *pd = foobar; + struct packet_data *pkt; + long min_sleep_time, residue; + + set_user_nice(current, -20); + + for (;;) { + DECLARE_WAITQUEUE(wait, current); + + /* + * Wait until there is something to do + */ + add_wait_queue(&pd->wqueue, &wait); + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + + /* Check if we need to run pkt_handle_queue */ + if (atomic_read(&pd->scan_queue) > 0) + goto work_to_do; + + /* Check if we need to run the state machine for some packet */ + list_for_each_entry(pkt, &pd->cdrw.pkt_active_list, list) { + if (atomic_read(&pkt->run_sm) > 0) + goto work_to_do; + } + + /* Check if we need to process the iosched queues */ + if (atomic_read(&pd->iosched.attention) != 0) + goto work_to_do; + + /* Otherwise, go to sleep */ + if (PACKET_DEBUG > 1) { + int states[PACKET_NUM_STATES]; + pkt_count_states(pd, states); + VPRINTK("kcdrwd: i:%d ow:%d rw:%d ww:%d rec:%d fin:%d\n", + states[0], states[1], states[2], states[3], + states[4], states[5]); + } + + min_sleep_time = MAX_SCHEDULE_TIMEOUT; + list_for_each_entry(pkt, &pd->cdrw.pkt_active_list, list) { + if (pkt->sleep_time && pkt->sleep_time < min_sleep_time) + min_sleep_time = pkt->sleep_time; + } + + generic_unplug_device(bdev_get_queue(pd->bdev)); + + VPRINTK("kcdrwd: sleeping\n"); + residue = schedule_timeout(min_sleep_time); + VPRINTK("kcdrwd: wake up\n"); + + /* make swsusp happy with our thread */ + if (current->flags & PF_FREEZE) + refrigerator(PF_FREEZE); + + list_for_each_entry(pkt, &pd->cdrw.pkt_active_list, list) { + if (!pkt->sleep_time) + continue; + pkt->sleep_time -= min_sleep_time - residue; + if (pkt->sleep_time <= 0) { + pkt->sleep_time = 0; + atomic_inc(&pkt->run_sm); + } + } + + if (signal_pending(current)) { + flush_signals(current); + } + if (kthread_should_stop()) + break; + } +work_to_do: + set_current_state(TASK_RUNNING); + remove_wait_queue(&pd->wqueue, &wait); + + if (kthread_should_stop()) + break; + + /* + * if pkt_handle_queue returns true, we can queue + * another request. + */ + while (pkt_handle_queue(pd)) + ; + + /* + * Handle packet state machine + */ + pkt_handle_packets(pd); + + /* + * Handle iosched queues + */ + pkt_iosched_process_queue(pd); + } + + return 0; +} + +static void pkt_print_settings(struct pktcdvd_device *pd) +{ + printk("pktcdvd: %s packets, ", pd->settings.fp ? "Fixed" : "Variable"); + printk("%u blocks, ", pd->settings.size >> 2); + printk("Mode-%c disc\n", pd->settings.block_mode == 8 ? '1' : '2'); +} + +static int pkt_mode_sense(struct pktcdvd_device *pd, struct packet_command *cgc, + int page_code, int page_control) +{ + memset(cgc->cmd, 0, sizeof(cgc->cmd)); + + cgc->cmd[0] = GPCMD_MODE_SENSE_10; + cgc->cmd[2] = page_code | (page_control << 6); + cgc->cmd[7] = cgc->buflen >> 8; + cgc->cmd[8] = cgc->buflen & 0xff; + cgc->data_direction = CGC_DATA_READ; + return pkt_generic_packet(pd, cgc); +} + +static int pkt_mode_select(struct pktcdvd_device *pd, struct packet_command *cgc) +{ + memset(cgc->cmd, 0, sizeof(cgc->cmd)); + memset(cgc->buffer, 0, 2); + cgc->cmd[0] = GPCMD_MODE_SELECT_10; + cgc->cmd[1] = 0x10; /* PF */ + cgc->cmd[7] = cgc->buflen >> 8; + cgc->cmd[8] = cgc->buflen & 0xff; + cgc->data_direction = CGC_DATA_WRITE; + return pkt_generic_packet(pd, cgc); +} + +static int pkt_get_disc_info(struct pktcdvd_device *pd, disc_information *di) +{ + struct packet_command cgc; + int ret; + + /* set up command and get the disc info */ + init_cdrom_command(&cgc, di, sizeof(*di), CGC_DATA_READ); + cgc.cmd[0] = GPCMD_READ_DISC_INFO; + cgc.cmd[8] = cgc.buflen = 2; + cgc.quiet = 1; + + if ((ret = pkt_generic_packet(pd, &cgc))) + return ret; + + /* not all drives have the same disc_info length, so requeue + * packet with the length the drive tells us it can supply + */ + cgc.buflen = be16_to_cpu(di->disc_information_length) + + sizeof(di->disc_information_length); + + if (cgc.buflen > sizeof(disc_information)) + cgc.buflen = sizeof(disc_information); + + cgc.cmd[8] = cgc.buflen; + return pkt_generic_packet(pd, &cgc); +} + +static int pkt_get_track_info(struct pktcdvd_device *pd, __u16 track, __u8 type, track_information *ti) +{ + struct packet_command cgc; + int ret; + + init_cdrom_command(&cgc, ti, 8, CGC_DATA_READ); + cgc.cmd[0] = GPCMD_READ_TRACK_RZONE_INFO; + cgc.cmd[1] = type & 3; + cgc.cmd[4] = (track & 0xff00) >> 8; + cgc.cmd[5] = track & 0xff; + cgc.cmd[8] = 8; + cgc.quiet = 1; + + if ((ret = pkt_generic_packet(pd, &cgc))) + return ret; + + cgc.buflen = be16_to_cpu(ti->track_information_length) + + sizeof(ti->track_information_length); + + if (cgc.buflen > sizeof(track_information)) + cgc.buflen = sizeof(track_information); + + cgc.cmd[8] = cgc.buflen; + return pkt_generic_packet(pd, &cgc); +} + +static int pkt_get_last_written(struct pktcdvd_device *pd, long *last_written) +{ + disc_information di; + track_information ti; + __u32 last_track; + int ret = -1; + + if ((ret = pkt_get_disc_info(pd, &di))) + return ret; + + last_track = (di.last_track_msb << 8) | di.last_track_lsb; + if ((ret = pkt_get_track_info(pd, last_track, 1, &ti))) + return ret; + + /* if this track is blank, try the previous. */ + if (ti.blank) { + last_track--; + if ((ret = pkt_get_track_info(pd, last_track, 1, &ti))) + return ret; + } + + /* if last recorded field is valid, return it. */ + if (ti.lra_v) { + *last_written = be32_to_cpu(ti.last_rec_address); + } else { + /* make it up instead */ + *last_written = be32_to_cpu(ti.track_start) + + be32_to_cpu(ti.track_size); + if (ti.free_blocks) + *last_written -= (be32_to_cpu(ti.free_blocks) + 7); + } + return 0; +} + +/* + * write mode select package based on pd->settings + */ +static int pkt_set_write_settings(struct pktcdvd_device *pd) +{ + struct packet_command cgc; + struct request_sense sense; + write_param_page *wp; + char buffer[128]; + int ret, size; + + /* doesn't apply to DVD+RW */ + if (pd->mmc3_profile == 0x1a) + return 0; + + memset(buffer, 0, sizeof(buffer)); + init_cdrom_command(&cgc, buffer, sizeof(*wp), CGC_DATA_READ); + cgc.sense = &sense; + if ((ret = pkt_mode_sense(pd, &cgc, GPMODE_WRITE_PARMS_PAGE, 0))) { + pkt_dump_sense(&cgc); + return ret; + } + + size = 2 + ((buffer[0] << 8) | (buffer[1] & 0xff)); + pd->mode_offset = (buffer[6] << 8) | (buffer[7] & 0xff); + if (size > sizeof(buffer)) + size = sizeof(buffer); + + /* + * now get it all + */ + init_cdrom_command(&cgc, buffer, size, CGC_DATA_READ); + cgc.sense = &sense; + if ((ret = pkt_mode_sense(pd, &cgc, GPMODE_WRITE_PARMS_PAGE, 0))) { + pkt_dump_sense(&cgc); + return ret; + } + + /* + * write page is offset header + block descriptor length + */ + wp = (write_param_page *) &buffer[sizeof(struct mode_page_header) + pd->mode_offset]; + + wp->fp = pd->settings.fp; + wp->track_mode = pd->settings.track_mode; + wp->write_type = pd->settings.write_type; + wp->data_block_type = pd->settings.block_mode; + + wp->multi_session = 0; + +#ifdef PACKET_USE_LS + wp->link_size = 7; + wp->ls_v = 1; +#endif + + if (wp->data_block_type == PACKET_BLOCK_MODE1) { + wp->session_format = 0; + wp->subhdr2 = 0x20; + } else if (wp->data_block_type == PACKET_BLOCK_MODE2) { + wp->session_format = 0x20; + wp->subhdr2 = 8; +#if 0 + wp->mcn[0] = 0x80; + memcpy(&wp->mcn[1], PACKET_MCN, sizeof(wp->mcn) - 1); +#endif + } else { + /* + * paranoia + */ + printk("pktcdvd: write mode wrong %d\n", wp->data_block_type); + return 1; + } + wp->packet_size = cpu_to_be32(pd->settings.size >> 2); + + cgc.buflen = cgc.cmd[8] = size; + if ((ret = pkt_mode_select(pd, &cgc))) { + pkt_dump_sense(&cgc); + return ret; + } + + pkt_print_settings(pd); + return 0; +} + +/* + * 0 -- we can write to this track, 1 -- we can't + */ +static int pkt_good_track(track_information *ti) +{ + /* + * only good for CD-RW at the moment, not DVD-RW + */ + + /* + * FIXME: only for FP + */ + if (ti->fp == 0) + return 0; + + /* + * "good" settings as per Mt Fuji. + */ + if (ti->rt == 0 && ti->blank == 0 && ti->packet == 1) + return 0; + + if (ti->rt == 0 && ti->blank == 1 && ti->packet == 1) + return 0; + + if (ti->rt == 1 && ti->blank == 0 && ti->packet == 1) + return 0; + + printk("pktcdvd: bad state %d-%d-%d\n", ti->rt, ti->blank, ti->packet); + return 1; +} + +/* + * 0 -- we can write to this disc, 1 -- we can't + */ +static int pkt_good_disc(struct pktcdvd_device *pd, disc_information *di) +{ + switch (pd->mmc3_profile) { + case 0x0a: /* CD-RW */ + case 0xffff: /* MMC3 not supported */ + break; + case 0x1a: /* DVD+RW */ + case 0x13: /* DVD-RW */ + return 0; + default: + printk("pktcdvd: Wrong disc profile (%x)\n", pd->mmc3_profile); + return 1; + } + + /* + * for disc type 0xff we should probably reserve a new track. + * but i'm not sure, should we leave this to user apps? probably. + */ + if (di->disc_type == 0xff) { + printk("pktcdvd: Unknown disc. No track?\n"); + return 1; + } + + if (di->disc_type != 0x20 && di->disc_type != 0) { + printk("pktcdvd: Wrong disc type (%x)\n", di->disc_type); + return 1; + } + + if (di->erasable == 0) { + printk("pktcdvd: Disc not erasable\n"); + return 1; + } + + if (di->border_status == PACKET_SESSION_RESERVED) { + printk("pktcdvd: Can't write to last track (reserved)\n"); + return 1; + } + + return 0; +} + +static int pkt_probe_settings(struct pktcdvd_device *pd) +{ + struct packet_command cgc; + unsigned char buf[12]; + disc_information di; + track_information ti; + int ret, track; + + init_cdrom_command(&cgc, buf, sizeof(buf), CGC_DATA_READ); + cgc.cmd[0] = GPCMD_GET_CONFIGURATION; + cgc.cmd[8] = 8; + ret = pkt_generic_packet(pd, &cgc); + pd->mmc3_profile = ret ? 0xffff : buf[6] << 8 | buf[7]; + + memset(&di, 0, sizeof(disc_information)); + memset(&ti, 0, sizeof(track_information)); + + if ((ret = pkt_get_disc_info(pd, &di))) { + printk("failed get_disc\n"); + return ret; + } + + if (pkt_good_disc(pd, &di)) + return -ENXIO; + + switch (pd->mmc3_profile) { + case 0x1a: /* DVD+RW */ + printk("pktcdvd: inserted media is DVD+RW\n"); + break; + case 0x13: /* DVD-RW */ + printk("pktcdvd: inserted media is DVD-RW\n"); + break; + default: + printk("pktcdvd: inserted media is CD-R%s\n", di.erasable ? "W" : ""); + break; + } + pd->type = di.erasable ? PACKET_CDRW : PACKET_CDR; + + track = 1; /* (di.last_track_msb << 8) | di.last_track_lsb; */ + if ((ret = pkt_get_track_info(pd, track, 1, &ti))) { + printk("pktcdvd: failed get_track\n"); + return ret; + } + + if (pkt_good_track(&ti)) { + printk("pktcdvd: can't write to this track\n"); + return -ENXIO; + } + + /* + * we keep packet size in 512 byte units, makes it easier to + * deal with request calculations. + */ + pd->settings.size = be32_to_cpu(ti.fixed_packet_size) << 2; + if (pd->settings.size == 0) { + printk("pktcdvd: detected zero packet size!\n"); + pd->settings.size = 128; + } + pd->settings.fp = ti.fp; + pd->offset = (be32_to_cpu(ti.track_start) << 2) & (pd->settings.size - 1); + + if (ti.nwa_v) { + pd->nwa = be32_to_cpu(ti.next_writable); + set_bit(PACKET_NWA_VALID, &pd->flags); + } + + /* + * in theory we could use lra on -RW media as well and just zero + * blocks that haven't been written yet, but in practice that + * is just a no-go. we'll use that for -R, naturally. + */ + if (ti.lra_v) { + pd->lra = be32_to_cpu(ti.last_rec_address); + set_bit(PACKET_LRA_VALID, &pd->flags); + } else { + pd->lra = 0xffffffff; + set_bit(PACKET_LRA_VALID, &pd->flags); + } + + /* + * fine for now + */ + pd->settings.link_loss = 7; + pd->settings.write_type = 0; /* packet */ + pd->settings.track_mode = ti.track_mode; + + /* + * mode1 or mode2 disc + */ + switch (ti.data_mode) { + case PACKET_MODE1: + pd->settings.block_mode = PACKET_BLOCK_MODE1; + break; + case PACKET_MODE2: + pd->settings.block_mode = PACKET_BLOCK_MODE2; + break; + default: + printk("pktcdvd: unknown data mode\n"); + return 1; + } + return 0; +} + +/* + * enable/disable write caching on drive + */ +static int pkt_write_caching(struct pktcdvd_device *pd, int set) +{ + struct packet_command cgc; + struct request_sense sense; + unsigned char buf[64]; + int ret; + + memset(buf, 0, sizeof(buf)); + init_cdrom_command(&cgc, buf, sizeof(buf), CGC_DATA_READ); + cgc.sense = &sense; + cgc.buflen = pd->mode_offset + 12; + + /* + * caching mode page might not be there, so quiet this command + */ + cgc.quiet = 1; + + if ((ret = pkt_mode_sense(pd, &cgc, GPMODE_WCACHING_PAGE, 0))) + return ret; + + buf[pd->mode_offset + 10] |= (!!set << 2); + + cgc.buflen = cgc.cmd[8] = 2 + ((buf[0] << 8) | (buf[1] & 0xff)); + ret = pkt_mode_select(pd, &cgc); + if (ret) { + printk("pktcdvd: write caching control failed\n"); + pkt_dump_sense(&cgc); + } else if (!ret && set) + printk("pktcdvd: enabled write caching on %s\n", pd->name); + return ret; +} + +static int pkt_lock_door(struct pktcdvd_device *pd, int lockflag) +{ + struct packet_command cgc; + + init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE); + cgc.cmd[0] = GPCMD_PREVENT_ALLOW_MEDIUM_REMOVAL; + cgc.cmd[4] = lockflag ? 1 : 0; + return pkt_generic_packet(pd, &cgc); +} + +/* + * Returns drive maximum write speed + */ +static int pkt_get_max_speed(struct pktcdvd_device *pd, unsigned *write_speed) +{ + struct packet_command cgc; + struct request_sense sense; + unsigned char buf[256+18]; + unsigned char *cap_buf; + int ret, offset; + + memset(buf, 0, sizeof(buf)); + cap_buf = &buf[sizeof(struct mode_page_header) + pd->mode_offset]; + init_cdrom_command(&cgc, buf, sizeof(buf), CGC_DATA_UNKNOWN); + cgc.sense = &sense; + + ret = pkt_mode_sense(pd, &cgc, GPMODE_CAPABILITIES_PAGE, 0); + if (ret) { + cgc.buflen = pd->mode_offset + cap_buf[1] + 2 + + sizeof(struct mode_page_header); + ret = pkt_mode_sense(pd, &cgc, GPMODE_CAPABILITIES_PAGE, 0); + if (ret) { + pkt_dump_sense(&cgc); + return ret; + } + } + + offset = 20; /* Obsoleted field, used by older drives */ + if (cap_buf[1] >= 28) + offset = 28; /* Current write speed selected */ + if (cap_buf[1] >= 30) { + /* If the drive reports at least one "Logical Unit Write + * Speed Performance Descriptor Block", use the information + * in the first block. (contains the highest speed) + */ + int num_spdb = (cap_buf[30] << 8) + cap_buf[31]; + if (num_spdb > 0) + offset = 34; + } + + *write_speed = (cap_buf[offset] << 8) | cap_buf[offset + 1]; + return 0; +} + +/* These tables from cdrecord - I don't have orange book */ +/* standard speed CD-RW (1-4x) */ +static char clv_to_speed[16] = { + /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 */ + 0, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; +/* high speed CD-RW (-10x) */ +static char hs_clv_to_speed[16] = { + /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 */ + 0, 2, 4, 6, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; +/* ultra high speed CD-RW */ +static char us_clv_to_speed[16] = { + /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 */ + 0, 2, 4, 8, 0, 0,16, 0,24,32,40,48, 0, 0, 0, 0 +}; + +/* + * reads the maximum media speed from ATIP + */ +static int pkt_media_speed(struct pktcdvd_device *pd, unsigned *speed) +{ + struct packet_command cgc; + struct request_sense sense; + unsigned char buf[64]; + unsigned int size, st, sp; + int ret; + + init_cdrom_command(&cgc, buf, 2, CGC_DATA_READ); + cgc.sense = &sense; + cgc.cmd[0] = GPCMD_READ_TOC_PMA_ATIP; + cgc.cmd[1] = 2; + cgc.cmd[2] = 4; /* READ ATIP */ + cgc.cmd[8] = 2; + ret = pkt_generic_packet(pd, &cgc); + if (ret) { + pkt_dump_sense(&cgc); + return ret; + } + size = ((unsigned int) buf[0]<<8) + buf[1] + 2; + if (size > sizeof(buf)) + size = sizeof(buf); + + init_cdrom_command(&cgc, buf, size, CGC_DATA_READ); + cgc.sense = &sense; + cgc.cmd[0] = GPCMD_READ_TOC_PMA_ATIP; + cgc.cmd[1] = 2; + cgc.cmd[2] = 4; + cgc.cmd[8] = size; + ret = pkt_generic_packet(pd, &cgc); + if (ret) { + pkt_dump_sense(&cgc); + return ret; + } + + if (!buf[6] & 0x40) { + printk("pktcdvd: Disc type is not CD-RW\n"); + return 1; + } + if (!buf[6] & 0x4) { + printk("pktcdvd: A1 values on media are not valid, maybe not CDRW?\n"); + return 1; + } + + st = (buf[6] >> 3) & 0x7; /* disc sub-type */ + + sp = buf[16] & 0xf; /* max speed from ATIP A1 field */ + + /* Info from cdrecord */ + switch (st) { + case 0: /* standard speed */ + *speed = clv_to_speed[sp]; + break; + case 1: /* high speed */ + *speed = hs_clv_to_speed[sp]; + break; + case 2: /* ultra high speed */ + *speed = us_clv_to_speed[sp]; + break; + default: + printk("pktcdvd: Unknown disc sub-type %d\n",st); + return 1; + } + if (*speed) { + printk("pktcdvd: Max. media speed: %d\n",*speed); + return 0; + } else { + printk("pktcdvd: Unknown speed %d for sub-type %d\n",sp,st); + return 1; + } +} + +static int pkt_perform_opc(struct pktcdvd_device *pd) +{ + struct packet_command cgc; + struct request_sense sense; + int ret; + + VPRINTK("pktcdvd: Performing OPC\n"); + + init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE); + cgc.sense = &sense; + cgc.timeout = 60*HZ; + cgc.cmd[0] = GPCMD_SEND_OPC; + cgc.cmd[1] = 1; + if ((ret = pkt_generic_packet(pd, &cgc))) + pkt_dump_sense(&cgc); + return ret; +} + +static int pkt_open_write(struct pktcdvd_device *pd) +{ + int ret; + unsigned int write_speed, media_write_speed, read_speed; + + if ((ret = pkt_probe_settings(pd))) { + DPRINTK("pktcdvd: %s failed probe\n", pd->name); + return -EIO; + } + + if ((ret = pkt_set_write_settings(pd))) { + DPRINTK("pktcdvd: %s failed saving write settings\n", pd->name); + return -EIO; + } + + pkt_write_caching(pd, USE_WCACHING); + + if ((ret = pkt_get_max_speed(pd, &write_speed))) + write_speed = 16 * 177; + switch (pd->mmc3_profile) { + case 0x13: /* DVD-RW */ + case 0x1a: /* DVD+RW */ + DPRINTK("pktcdvd: write speed %ukB/s\n", write_speed); + break; + default: + if ((ret = pkt_media_speed(pd, &media_write_speed))) + media_write_speed = 16; + write_speed = min(write_speed, media_write_speed * 177); + DPRINTK("pktcdvd: write speed %ux\n", write_speed / 176); + break; + } + read_speed = write_speed; + + if ((ret = pkt_set_speed(pd, write_speed, read_speed))) { + DPRINTK("pktcdvd: %s couldn't set write speed\n", pd->name); + return -EIO; + } + pd->write_speed = write_speed; + pd->read_speed = read_speed; + + if ((ret = pkt_perform_opc(pd))) { + DPRINTK("pktcdvd: %s Optimum Power Calibration failed\n", pd->name); + } + + return 0; +} + +/* + * called at open time. + */ +static int pkt_open_dev(struct pktcdvd_device *pd, int write) +{ + int ret; + long lba; + request_queue_t *q; + + /* + * We need to re-open the cdrom device without O_NONBLOCK to be able + * to read/write from/to it. It is already opened in O_NONBLOCK mode + * so bdget() can't fail. + */ + bdget(pd->bdev->bd_dev); + if ((ret = blkdev_get(pd->bdev, FMODE_READ, O_RDONLY))) + goto out; + + if ((ret = pkt_get_last_written(pd, &lba))) { + printk("pktcdvd: pkt_get_last_written failed\n"); + goto out_putdev; + } + + set_capacity(pd->disk, lba << 2); + set_capacity(pd->bdev->bd_disk, lba << 2); + bd_set_size(pd->bdev, (loff_t)lba << 11); + + q = bdev_get_queue(pd->bdev); + if (write) { + if ((ret = pkt_open_write(pd))) + goto out_putdev; + /* + * Some CDRW drives can not handle writes larger than one packet, + * even if the size is a multiple of the packet size. + */ + spin_lock_irq(q->queue_lock); + blk_queue_max_sectors(q, pd->settings.size); + spin_unlock_irq(q->queue_lock); + set_bit(PACKET_WRITABLE, &pd->flags); + } else { + pkt_set_speed(pd, MAX_SPEED, MAX_SPEED); + clear_bit(PACKET_WRITABLE, &pd->flags); + } + + if ((ret = pkt_set_segment_merging(pd, q))) + goto out_putdev; + + if (write) + printk("pktcdvd: %lukB available on disc\n", lba << 1); + + return 0; + +out_putdev: + blkdev_put(pd->bdev); +out: + return ret; +} + +/* + * called when the device is closed. makes sure that the device flushes + * the internal cache before we close. + */ +static void pkt_release_dev(struct pktcdvd_device *pd, int flush) +{ + if (flush && pkt_flush_cache(pd)) + DPRINTK("pktcdvd: %s not flushing cache\n", pd->name); + + pkt_lock_door(pd, 0); + + pkt_set_speed(pd, MAX_SPEED, MAX_SPEED); + blkdev_put(pd->bdev); +} + +static struct pktcdvd_device *pkt_find_dev_from_minor(int dev_minor) +{ + if (dev_minor >= MAX_WRITERS) + return NULL; + return pkt_devs[dev_minor]; +} + +static int pkt_open(struct inode *inode, struct file *file) +{ + struct pktcdvd_device *pd = NULL; + int ret; + + VPRINTK("pktcdvd: entering open\n"); + + down(&ctl_mutex); + pd = pkt_find_dev_from_minor(iminor(inode)); + if (!pd) { + ret = -ENODEV; + goto out; + } + BUG_ON(pd->refcnt < 0); + + pd->refcnt++; + if (pd->refcnt == 1) { + if (pkt_open_dev(pd, file->f_mode & FMODE_WRITE)) { + ret = -EIO; + goto out_dec; + } + /* + * needed here as well, since ext2 (among others) may change + * the blocksize at mount time + */ + set_blocksize(inode->i_bdev, CD_FRAMESIZE); + } + + up(&ctl_mutex); + return 0; + +out_dec: + pd->refcnt--; +out: + VPRINTK("pktcdvd: failed open (%d)\n", ret); + up(&ctl_mutex); + return ret; +} + +static int pkt_close(struct inode *inode, struct file *file) +{ + struct pktcdvd_device *pd = inode->i_bdev->bd_disk->private_data; + int ret = 0; + + down(&ctl_mutex); + pd->refcnt--; + BUG_ON(pd->refcnt < 0); + if (pd->refcnt == 0) { + int flush = test_bit(PACKET_WRITABLE, &pd->flags); + pkt_release_dev(pd, flush); + } + up(&ctl_mutex); + return ret; +} + + +static void *psd_pool_alloc(int gfp_mask, void *data) +{ + return kmalloc(sizeof(struct packet_stacked_data), gfp_mask); +} + +static void psd_pool_free(void *ptr, void *data) +{ + kfree(ptr); +} + +static int pkt_end_io_read_cloned(struct bio *bio, unsigned int bytes_done, int err) +{ + struct packet_stacked_data *psd = bio->bi_private; + struct pktcdvd_device *pd = psd->pd; + + if (bio->bi_size) + return 1; + + bio_put(bio); + bio_endio(psd->bio, psd->bio->bi_size, err); + mempool_free(psd, psd_pool); + pkt_bio_finished(pd); + return 0; +} + +static int pkt_make_request(request_queue_t *q, struct bio *bio) +{ + struct pktcdvd_device *pd; + char b[BDEVNAME_SIZE]; + sector_t zone; + struct packet_data *pkt; + int was_empty, blocked_bio; + struct pkt_rb_node *node; + + pd = q->queuedata; + if (!pd) { + printk("pktcdvd: %s incorrect request queue\n", bdevname(bio->bi_bdev, b)); + goto end_io; + } + + /* + * Clone READ bios so we can have our own bi_end_io callback. + */ + if (bio_data_dir(bio) == READ) { + struct bio *cloned_bio = bio_clone(bio, GFP_NOIO); + struct packet_stacked_data *psd = mempool_alloc(psd_pool, GFP_NOIO); + + psd->pd = pd; + psd->bio = bio; + cloned_bio->bi_bdev = pd->bdev; + cloned_bio->bi_private = psd; + cloned_bio->bi_end_io = pkt_end_io_read_cloned; + pd->stats.secs_r += bio->bi_size >> 9; + pkt_queue_bio(pd, cloned_bio, 1); + return 0; + } + + if (!test_bit(PACKET_WRITABLE, &pd->flags)) { + printk("pktcdvd: WRITE for ro device %s (%llu)\n", + pd->name, (unsigned long long)bio->bi_sector); + goto end_io; + } + + if (!bio->bi_size || (bio->bi_size % CD_FRAMESIZE)) { + printk("pktcdvd: wrong bio size\n"); + goto end_io; + } + + blk_queue_bounce(q, &bio); + + zone = ZONE(bio->bi_sector, pd); + VPRINTK("pkt_make_request: start = %6llx stop = %6llx\n", + (unsigned long long)bio->bi_sector, + (unsigned long long)(bio->bi_sector + bio_sectors(bio))); + + /* Check if we have to split the bio */ + { + struct bio_pair *bp; + sector_t last_zone; + int first_sectors; + + last_zone = ZONE(bio->bi_sector + bio_sectors(bio) - 1, pd); + if (last_zone != zone) { + BUG_ON(last_zone != zone + pd->settings.size); + first_sectors = last_zone - bio->bi_sector; + bp = bio_split(bio, bio_split_pool, first_sectors); + BUG_ON(!bp); + pkt_make_request(q, &bp->bio1); + pkt_make_request(q, &bp->bio2); + bio_pair_release(bp); + return 0; + } + } + + /* + * If we find a matching packet in state WAITING or READ_WAIT, we can + * just append this bio to that packet. + */ + spin_lock(&pd->cdrw.active_list_lock); + blocked_bio = 0; + list_for_each_entry(pkt, &pd->cdrw.pkt_active_list, list) { + if (pkt->sector == zone) { + spin_lock(&pkt->lock); + if ((pkt->state == PACKET_WAITING_STATE) || + (pkt->state == PACKET_READ_WAIT_STATE)) { + pkt_add_list_last(bio, &pkt->orig_bios, + &pkt->orig_bios_tail); + pkt->write_size += bio->bi_size / CD_FRAMESIZE; + if ((pkt->write_size >= pkt->frames) && + (pkt->state == PACKET_WAITING_STATE)) { + atomic_inc(&pkt->run_sm); + wake_up(&pd->wqueue); + } + spin_unlock(&pkt->lock); + spin_unlock(&pd->cdrw.active_list_lock); + return 0; + } else { + blocked_bio = 1; + } + spin_unlock(&pkt->lock); + } + } + spin_unlock(&pd->cdrw.active_list_lock); + + /* + * No matching packet found. Store the bio in the work queue. + */ + node = mempool_alloc(pd->rb_pool, GFP_NOIO); + BUG_ON(!node); + node->bio = bio; + spin_lock(&pd->lock); + BUG_ON(pd->bio_queue_size < 0); + was_empty = (pd->bio_queue_size == 0); + pkt_rbtree_insert(pd, node); + spin_unlock(&pd->lock); + + /* + * Wake up the worker thread. + */ + atomic_set(&pd->scan_queue, 1); + if (was_empty) { + /* This wake_up is required for correct operation */ + wake_up(&pd->wqueue); + } else if (!list_empty(&pd->cdrw.pkt_free_list) && !blocked_bio) { + /* + * This wake up is not required for correct operation, + * but improves performance in some cases. + */ + wake_up(&pd->wqueue); + } + return 0; +end_io: + bio_io_error(bio, bio->bi_size); + return 0; +} + + + +static int pkt_merge_bvec(request_queue_t *q, struct bio *bio, struct bio_vec *bvec) +{ + struct pktcdvd_device *pd = q->queuedata; + sector_t zone = ZONE(bio->bi_sector, pd); + int used = ((bio->bi_sector - zone) << 9) + bio->bi_size; + int remaining = (pd->settings.size << 9) - used; + int remaining2; + + /* + * A bio <= PAGE_SIZE must be allowed. If it crosses a packet + * boundary, pkt_make_request() will split the bio. + */ + remaining2 = PAGE_SIZE - bio->bi_size; + remaining = max(remaining, remaining2); + + BUG_ON(remaining < 0); + return remaining; +} + +static void pkt_init_queue(struct pktcdvd_device *pd) +{ + request_queue_t *q = pd->disk->queue; + + blk_queue_make_request(q, pkt_make_request); + blk_queue_hardsect_size(q, CD_FRAMESIZE); + blk_queue_max_sectors(q, PACKET_MAX_SECTORS); + blk_queue_merge_bvec(q, pkt_merge_bvec); + q->queuedata = pd; +} + +static int pkt_seq_show(struct seq_file *m, void *p) +{ + struct pktcdvd_device *pd = m->private; + char *msg; + char bdev_buf[BDEVNAME_SIZE]; + int states[PACKET_NUM_STATES]; + + seq_printf(m, "Writer %s mapped to %s:\n", pd->name, + bdevname(pd->bdev, bdev_buf)); + + seq_printf(m, "\nSettings:\n"); + seq_printf(m, "\tpacket size:\t\t%dkB\n", pd->settings.size / 2); + + if (pd->settings.write_type == 0) + msg = "Packet"; + else + msg = "Unknown"; + seq_printf(m, "\twrite type:\t\t%s\n", msg); + + seq_printf(m, "\tpacket type:\t\t%s\n", pd->settings.fp ? "Fixed" : "Variable"); + seq_printf(m, "\tlink loss:\t\t%d\n", pd->settings.link_loss); + + seq_printf(m, "\ttrack mode:\t\t%d\n", pd->settings.track_mode); + + if (pd->settings.block_mode == PACKET_BLOCK_MODE1) + msg = "Mode 1"; + else if (pd->settings.block_mode == PACKET_BLOCK_MODE2) + msg = "Mode 2"; + else + msg = "Unknown"; + seq_printf(m, "\tblock mode:\t\t%s\n", msg); + + seq_printf(m, "\nStatistics:\n"); + seq_printf(m, "\tpackets started:\t%lu\n", pd->stats.pkt_started); + seq_printf(m, "\tpackets ended:\t\t%lu\n", pd->stats.pkt_ended); + seq_printf(m, "\twritten:\t\t%lukB\n", pd->stats.secs_w >> 1); + seq_printf(m, "\tread gather:\t\t%lukB\n", pd->stats.secs_rg >> 1); + seq_printf(m, "\tread:\t\t\t%lukB\n", pd->stats.secs_r >> 1); + + seq_printf(m, "\nMisc:\n"); + seq_printf(m, "\treference count:\t%d\n", pd->refcnt); + seq_printf(m, "\tflags:\t\t\t0x%lx\n", pd->flags); + seq_printf(m, "\tread speed:\t\t%ukB/s\n", pd->read_speed); + seq_printf(m, "\twrite speed:\t\t%ukB/s\n", pd->write_speed); + seq_printf(m, "\tstart offset:\t\t%lu\n", pd->offset); + seq_printf(m, "\tmode page offset:\t%u\n", pd->mode_offset); + + seq_printf(m, "\nQueue state:\n"); + seq_printf(m, "\tbios queued:\t\t%d\n", pd->bio_queue_size); + seq_printf(m, "\tbios pending:\t\t%d\n", atomic_read(&pd->cdrw.pending_bios)); + seq_printf(m, "\tcurrent sector:\t\t0x%llx\n", (unsigned long long)pd->current_sector); + + pkt_count_states(pd, states); + seq_printf(m, "\tstate:\t\t\ti:%d ow:%d rw:%d ww:%d rec:%d fin:%d\n", + states[0], states[1], states[2], states[3], states[4], states[5]); + + return 0; +} + +static int pkt_seq_open(struct inode *inode, struct file *file) +{ + return single_open(file, pkt_seq_show, PDE(inode)->data); +} + +static struct file_operations pkt_proc_fops = { + .open = pkt_seq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release +}; + +static int pkt_new_dev(struct pktcdvd_device *pd, dev_t dev) +{ + int i; + int ret = 0; + char b[BDEVNAME_SIZE]; + struct proc_dir_entry *proc; + struct block_device *bdev; + + if (pd->pkt_dev == dev) { + printk("pktcdvd: Recursive setup not allowed\n"); + return -EBUSY; + } + for (i = 0; i < MAX_WRITERS; i++) { + struct pktcdvd_device *pd2 = pkt_devs[i]; + if (!pd2) + continue; + if (pd2->bdev->bd_dev == dev) { + printk("pktcdvd: %s already setup\n", bdevname(pd2->bdev, b)); + return -EBUSY; + } + if (pd2->pkt_dev == dev) { + printk("pktcdvd: Can't chain pktcdvd devices\n"); + return -EBUSY; + } + } + + bdev = bdget(dev); + if (!bdev) + return -ENOMEM; + ret = blkdev_get(bdev, FMODE_READ, O_RDONLY | O_NONBLOCK); + if (ret) + return ret; + + /* This is safe, since we have a reference from open(). */ + __module_get(THIS_MODULE); + + if (!pkt_grow_pktlist(pd, CONFIG_CDROM_PKTCDVD_BUFFERS)) { + printk("pktcdvd: not enough memory for buffers\n"); + ret = -ENOMEM; + goto out_mem; + } + + pd->bdev = bdev; + set_blocksize(bdev, CD_FRAMESIZE); + + pkt_init_queue(pd); + + atomic_set(&pd->cdrw.pending_bios, 0); + pd->cdrw.thread = kthread_run(kcdrwd, pd, "%s", pd->name); + if (IS_ERR(pd->cdrw.thread)) { + printk("pktcdvd: can't start kernel thread\n"); + ret = -ENOMEM; + goto out_thread; + } + + proc = create_proc_entry(pd->name, 0, pkt_proc); + if (proc) { + proc->data = pd; + proc->proc_fops = &pkt_proc_fops; + } + DPRINTK("pktcdvd: writer %s mapped to %s\n", pd->name, bdevname(bdev, b)); + return 0; + +out_thread: + pkt_shrink_pktlist(pd); +out_mem: + blkdev_put(bdev); + /* This is safe: open() is still holding a reference. */ + module_put(THIS_MODULE); + return ret; +} + +static int pkt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct pktcdvd_device *pd = inode->i_bdev->bd_disk->private_data; + + VPRINTK("pkt_ioctl: cmd %x, dev %d:%d\n", cmd, imajor(inode), iminor(inode)); + BUG_ON(!pd); + + switch (cmd) { + /* + * forward selected CDROM ioctls to CD-ROM, for UDF + */ + case CDROMMULTISESSION: + case CDROMREADTOCENTRY: + case CDROM_LAST_WRITTEN: + case CDROM_SEND_PACKET: + case SCSI_IOCTL_SEND_COMMAND: + return ioctl_by_bdev(pd->bdev, cmd, arg); + + case CDROMEJECT: + /* + * The door gets locked when the device is opened, so we + * have to unlock it or else the eject command fails. + */ + pkt_lock_door(pd, 0); + return ioctl_by_bdev(pd->bdev, cmd, arg); + + default: + printk("pktcdvd: Unknown ioctl for %s (%x)\n", pd->name, cmd); + return -ENOTTY; + } + + return 0; +} + +static int pkt_media_changed(struct gendisk *disk) +{ + struct pktcdvd_device *pd = disk->private_data; + struct gendisk *attached_disk; + + if (!pd) + return 0; + if (!pd->bdev) + return 0; + attached_disk = pd->bdev->bd_disk; + if (!attached_disk) + return 0; + return attached_disk->fops->media_changed(attached_disk); +} + +static struct block_device_operations pktcdvd_ops = { + .owner = THIS_MODULE, + .open = pkt_open, + .release = pkt_close, + .ioctl = pkt_ioctl, + .media_changed = pkt_media_changed, +}; + +/* + * Set up mapping from pktcdvd device to CD-ROM device. + */ +static int pkt_setup_dev(struct pkt_ctrl_command *ctrl_cmd) +{ + int idx; + int ret = -ENOMEM; + struct pktcdvd_device *pd; + struct gendisk *disk; + dev_t dev = new_decode_dev(ctrl_cmd->dev); + + for (idx = 0; idx < MAX_WRITERS; idx++) + if (!pkt_devs[idx]) + break; + if (idx == MAX_WRITERS) { + printk("pktcdvd: max %d writers supported\n", MAX_WRITERS); + return -EBUSY; + } + + pd = kmalloc(sizeof(struct pktcdvd_device), GFP_KERNEL); + if (!pd) + return ret; + memset(pd, 0, sizeof(struct pktcdvd_device)); + + pd->rb_pool = mempool_create(PKT_RB_POOL_SIZE, pkt_rb_alloc, pkt_rb_free, NULL); + if (!pd->rb_pool) + goto out_mem; + + disk = alloc_disk(1); + if (!disk) + goto out_mem; + pd->disk = disk; + + spin_lock_init(&pd->lock); + spin_lock_init(&pd->iosched.lock); + sprintf(pd->name, "pktcdvd%d", idx); + init_waitqueue_head(&pd->wqueue); + pd->bio_queue = RB_ROOT; + + disk->major = pkt_major; + disk->first_minor = idx; + disk->fops = &pktcdvd_ops; + disk->flags = GENHD_FL_REMOVABLE; + sprintf(disk->disk_name, "pktcdvd%d", idx); + disk->private_data = pd; + disk->queue = blk_alloc_queue(GFP_KERNEL); + if (!disk->queue) + goto out_mem2; + + pd->pkt_dev = MKDEV(disk->major, disk->first_minor); + ret = pkt_new_dev(pd, dev); + if (ret) + goto out_new_dev; + + add_disk(disk); + pkt_devs[idx] = pd; + ctrl_cmd->pkt_dev = new_encode_dev(pd->pkt_dev); + return 0; + +out_new_dev: + blk_put_queue(disk->queue); +out_mem2: + put_disk(disk); +out_mem: + if (pd->rb_pool) + mempool_destroy(pd->rb_pool); + kfree(pd); + return ret; +} + +/* + * Tear down mapping from pktcdvd device to CD-ROM device. + */ +static int pkt_remove_dev(struct pkt_ctrl_command *ctrl_cmd) +{ + struct pktcdvd_device *pd; + int idx; + dev_t pkt_dev = new_decode_dev(ctrl_cmd->pkt_dev); + + for (idx = 0; idx < MAX_WRITERS; idx++) { + pd = pkt_devs[idx]; + if (pd && (pd->pkt_dev == pkt_dev)) + break; + } + if (idx == MAX_WRITERS) { + DPRINTK("pktcdvd: dev not setup\n"); + return -ENXIO; + } + + if (pd->refcnt > 0) + return -EBUSY; + + if (!IS_ERR(pd->cdrw.thread)) + kthread_stop(pd->cdrw.thread); + + blkdev_put(pd->bdev); + + pkt_shrink_pktlist(pd); + + remove_proc_entry(pd->name, pkt_proc); + DPRINTK("pktcdvd: writer %s unmapped\n", pd->name); + + del_gendisk(pd->disk); + blk_put_queue(pd->disk->queue); + put_disk(pd->disk); + + pkt_devs[idx] = NULL; + mempool_destroy(pd->rb_pool); + kfree(pd); + + /* This is safe: open() is still holding a reference. */ + module_put(THIS_MODULE); + return 0; +} + +static void pkt_get_status(struct pkt_ctrl_command *ctrl_cmd) +{ + struct pktcdvd_device *pd = pkt_find_dev_from_minor(ctrl_cmd->dev_index); + if (pd) { + ctrl_cmd->dev = new_encode_dev(pd->bdev->bd_dev); + ctrl_cmd->pkt_dev = new_encode_dev(pd->pkt_dev); + } else { + ctrl_cmd->dev = 0; + ctrl_cmd->pkt_dev = 0; + } + ctrl_cmd->num_devices = MAX_WRITERS; +} + +static int pkt_ctl_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + struct pkt_ctrl_command ctrl_cmd; + int ret = 0; + + if (cmd != PACKET_CTRL_CMD) + return -ENOTTY; + + if (copy_from_user(&ctrl_cmd, argp, sizeof(struct pkt_ctrl_command))) + return -EFAULT; + + switch (ctrl_cmd.command) { + case PKT_CTRL_CMD_SETUP: + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + down(&ctl_mutex); + ret = pkt_setup_dev(&ctrl_cmd); + up(&ctl_mutex); + break; + case PKT_CTRL_CMD_TEARDOWN: + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + down(&ctl_mutex); + ret = pkt_remove_dev(&ctrl_cmd); + up(&ctl_mutex); + break; + case PKT_CTRL_CMD_STATUS: + down(&ctl_mutex); + pkt_get_status(&ctrl_cmd); + up(&ctl_mutex); + break; + default: + return -ENOTTY; + } + + if (copy_to_user(argp, &ctrl_cmd, sizeof(struct pkt_ctrl_command))) + return -EFAULT; + return ret; +} + + +static struct file_operations pkt_ctl_fops = { + .ioctl = pkt_ctl_ioctl, + .owner = THIS_MODULE, +}; + +static struct miscdevice pkt_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "pktcdvd", + .devfs_name = "pktcdvd/control", + .fops = &pkt_ctl_fops +}; + +int pkt_init(void) +{ + int ret; + + psd_pool = mempool_create(PSD_POOL_SIZE, psd_pool_alloc, psd_pool_free, NULL); + if (!psd_pool) + return -ENOMEM; + + ret = register_blkdev(pkt_major, "pktcdvd"); + if (ret < 0) { + printk("pktcdvd: Unable to register block device\n"); + goto out2; + } + if (!pkt_major) + pkt_major = ret; + + ret = misc_register(&pkt_misc); + if (ret) { + printk("pktcdvd: Unable to register misc device\n"); + goto out; + } + + init_MUTEX(&ctl_mutex); + + pkt_proc = proc_mkdir("pktcdvd", proc_root_driver); + + DPRINTK("pktcdvd: %s\n", VERSION_CODE); + return 0; + +out: + unregister_blkdev(pkt_major, "pktcdvd"); +out2: + mempool_destroy(psd_pool); + return ret; +} + +void pkt_exit(void) +{ + remove_proc_entry("pktcdvd", proc_root_driver); + misc_deregister(&pkt_misc); + unregister_blkdev(pkt_major, "pktcdvd"); + mempool_destroy(psd_pool); +} + +MODULE_DESCRIPTION("Packet writing layer for CD/DVD drives"); +MODULE_AUTHOR("Jens Axboe "); +MODULE_LICENSE("GPL"); + +module_init(pkt_init); +module_exit(pkt_exit); diff --git a/drivers/cdrom/Makefile b/drivers/cdrom/Makefile index 5c484f3b3e58..4a8351753e07 100644 --- a/drivers/cdrom/Makefile +++ b/drivers/cdrom/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_BLK_DEV_IDECD) += cdrom.o obj-$(CONFIG_BLK_DEV_SR) += cdrom.o obj-$(CONFIG_PARIDE_PCD) += cdrom.o +obj-$(CONFIG_CDROM_PKTCDVD) += cdrom.o obj-$(CONFIG_AZTCD) += aztcd.o obj-$(CONFIG_CDU31A) += cdu31a.o cdrom.o diff --git a/drivers/ide/ide-cd.c b/drivers/ide/ide-cd.c index 468eec62246f..895f245fcced 100644 --- a/drivers/ide/ide-cd.c +++ b/drivers/ide/ide-cd.c @@ -2001,7 +2001,7 @@ ide_do_rw_cdrom (ide_drive_t *drive, struct request *rq, sector_t block) } CDROM_CONFIG_FLAGS(drive)->seeking = 0; } - if (IDE_LARGE_SEEK(info->last_block, block, IDECD_SEEK_THRESHOLD) && drive->dsc_overlap) { + if ((rq_data_dir(rq) == READ) && IDE_LARGE_SEEK(info->last_block, block, IDECD_SEEK_THRESHOLD) && drive->dsc_overlap) { action = cdrom_start_seek(drive, block); } else { if (rq_data_dir(rq) == READ) @@ -2962,8 +2962,10 @@ int ide_cdrom_probe_capabilities (ide_drive_t *drive) CDROM_CONFIG_FLAGS(drive)->no_eject = 0; if (cap.cd_r_write) CDROM_CONFIG_FLAGS(drive)->cd_r = 1; - if (cap.cd_rw_write) + if (cap.cd_rw_write) { CDROM_CONFIG_FLAGS(drive)->cd_rw = 1; + CDROM_CONFIG_FLAGS(drive)->ram = 1; + } if (cap.test_write) CDROM_CONFIG_FLAGS(drive)->test_write = 1; if (cap.dvd_ram_read || cap.dvd_r_read || cap.dvd_rom) diff --git a/drivers/scsi/sr.c b/drivers/scsi/sr.c index 20a10a3ed5e4..da06adf48834 100644 --- a/drivers/scsi/sr.c +++ b/drivers/scsi/sr.c @@ -876,10 +876,10 @@ static void get_capabilities(struct scsi_cd *cd) cd->cdi.mask |= CDC_CLOSE_TRAY; */ /* - * if DVD-RAM of MRW-W, we are randomly writeable + * if DVD-RAM, MRW-W or CD-RW, we are randomly writable */ - if ((cd->cdi.mask & (CDC_DVD_RAM | CDC_MRW_W | CDC_RAM)) != - (CDC_DVD_RAM | CDC_MRW_W | CDC_RAM)) { + if ((cd->cdi.mask & (CDC_DVD_RAM | CDC_MRW_W | CDC_RAM | CDC_CD_RW)) != + (CDC_DVD_RAM | CDC_MRW_W | CDC_RAM | CDC_CD_RW)) { cd->device->writeable = 1; } diff --git a/fs/compat_ioctl.c b/fs/compat_ioctl.c index 2edf63444ccb..5642ccc235e8 100644 --- a/fs/compat_ioctl.c +++ b/fs/compat_ioctl.c @@ -115,6 +115,7 @@ #include #include #include +#include #include diff --git a/include/linux/cdrom.h b/include/linux/cdrom.h index 73a1ade6a8e9..1c1f5efffd64 100644 --- a/include/linux/cdrom.h +++ b/include/linux/cdrom.h @@ -499,6 +499,7 @@ struct cdrom_generic_command #define GPMODE_VENDOR_PAGE 0x00 #define GPMODE_R_W_ERROR_PAGE 0x01 #define GPMODE_WRITE_PARMS_PAGE 0x05 +#define GPMODE_WCACHING_PAGE 0x08 #define GPMODE_AUDIO_CTL_PAGE 0x0e #define GPMODE_POWER_PAGE 0x1a #define GPMODE_FAULT_FAIL_PAGE 0x1c diff --git a/include/linux/compat_ioctl.h b/include/linux/compat_ioctl.h index 77f59742c407..71da7d1260cd 100644 --- a/include/linux/compat_ioctl.h +++ b/include/linux/compat_ioctl.h @@ -382,6 +382,8 @@ COMPATIBLE_IOCTL(CDROMREADALL) COMPATIBLE_IOCTL(DVD_READ_STRUCT) COMPATIBLE_IOCTL(DVD_WRITE_STRUCT) COMPATIBLE_IOCTL(DVD_AUTH) +/* pktcdvd */ +COMPATIBLE_IOCTL(PACKET_CTRL_CMD) /* Big L */ ULONG_IOCTL(LOOP_SET_FD) ULONG_IOCTL(LOOP_CHANGE_FD) diff --git a/include/linux/pktcdvd.h b/include/linux/pktcdvd.h new file mode 100644 index 000000000000..4e2d2a942ecb --- /dev/null +++ b/include/linux/pktcdvd.h @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2000 Jens Axboe + * Copyright (C) 2001-2004 Peter Osterlund + * + * May be copied or modified under the terms of the GNU General Public + * License. See linux/COPYING for more information. + * + * Packet writing layer for ATAPI and SCSI CD-R, CD-RW, DVD-R, and + * DVD-RW devices. + * + */ +#ifndef __PKTCDVD_H +#define __PKTCDVD_H + +#include + +/* + * 1 for normal debug messages, 2 is very verbose. 0 to turn it off. + */ +#define PACKET_DEBUG 1 + +#define MAX_WRITERS 8 + +#define PKT_RB_POOL_SIZE 512 + +/* + * How long we should hold a non-full packet before starting data gathering. + */ +#define PACKET_WAIT_TIME (HZ * 5 / 1000) + +/* + * use drive write caching -- we need deferred error handling to be + * able to sucessfully recover with this option (drive will return good + * status as soon as the cdb is validated). + */ +#if defined(CONFIG_CDROM_PKTCDVD_WCACHE) +#define USE_WCACHING 1 +#else +#define USE_WCACHING 0 +#endif + +/* + * No user-servicable parts beyond this point -> + */ + +/* + * device types + */ +#define PACKET_CDR 1 +#define PACKET_CDRW 2 +#define PACKET_DVDR 3 +#define PACKET_DVDRW 4 + +/* + * flags + */ +#define PACKET_WRITABLE 1 /* pd is writable */ +#define PACKET_NWA_VALID 2 /* next writable address valid */ +#define PACKET_LRA_VALID 3 /* last recorded address valid */ +#define PACKET_MERGE_SEGS 4 /* perform segment merging to keep */ + /* underlying cdrom device happy */ + +/* + * Disc status -- from READ_DISC_INFO + */ +#define PACKET_DISC_EMPTY 0 +#define PACKET_DISC_INCOMPLETE 1 +#define PACKET_DISC_COMPLETE 2 +#define PACKET_DISC_OTHER 3 + +/* + * write type, and corresponding data block type + */ +#define PACKET_MODE1 1 +#define PACKET_MODE2 2 +#define PACKET_BLOCK_MODE1 8 +#define PACKET_BLOCK_MODE2 10 + +/* + * Last session/border status + */ +#define PACKET_SESSION_EMPTY 0 +#define PACKET_SESSION_INCOMPLETE 1 +#define PACKET_SESSION_RESERVED 2 +#define PACKET_SESSION_COMPLETE 3 + +#define PACKET_MCN "4a656e734178626f65323030300000" + +#undef PACKET_USE_LS + +#define PKT_CTRL_CMD_SETUP 0 +#define PKT_CTRL_CMD_TEARDOWN 1 +#define PKT_CTRL_CMD_STATUS 2 + +struct pkt_ctrl_command { + __u32 command; /* in: Setup, teardown, status */ + __u32 dev_index; /* in/out: Device index */ + __u32 dev; /* in/out: Device nr for cdrw device */ + __u32 pkt_dev; /* in/out: Device nr for packet device */ + __u32 num_devices; /* out: Largest device index + 1 */ + __u32 padding; /* Not used */ +}; + +/* + * packet ioctls + */ +#define PACKET_IOCTL_MAGIC ('X') +#define PACKET_CTRL_CMD _IOWR(PACKET_IOCTL_MAGIC, 1, struct pkt_ctrl_command) + +#ifdef __KERNEL__ +#include +#include +#include + +struct packet_settings +{ + __u8 size; /* packet size in (512 byte) sectors */ + __u8 fp; /* fixed packets */ + __u8 link_loss; /* the rest is specified + * as per Mt Fuji */ + __u8 write_type; + __u8 track_mode; + __u8 block_mode; +}; + +/* + * Very crude stats for now + */ +struct packet_stats +{ + unsigned long pkt_started; + unsigned long pkt_ended; + unsigned long secs_w; + unsigned long secs_rg; + unsigned long secs_r; +}; + +struct packet_cdrw +{ + struct list_head pkt_free_list; + struct list_head pkt_active_list; + spinlock_t active_list_lock; /* Serialize access to pkt_active_list */ + struct task_struct *thread; + atomic_t pending_bios; +}; + +/* + * Switch to high speed reading after reading this many kilobytes + * with no interspersed writes. + */ +#define HI_SPEED_SWITCH 512 + +struct packet_iosched +{ + atomic_t attention; /* Set to non-zero when queue processing is needed */ + int writing; /* Non-zero when writing, zero when reading */ + spinlock_t lock; /* Protecting read/write queue manipulations */ + struct bio *read_queue; + struct bio *read_queue_tail; + struct bio *write_queue; + struct bio *write_queue_tail; + int high_prio_read; /* An important read request has been queued */ + int successive_reads; +}; + +/* + * 32 buffers of 2048 bytes + */ +#define PACKET_MAX_SIZE 32 +#define PAGES_PER_PACKET (PACKET_MAX_SIZE * CD_FRAMESIZE / PAGE_SIZE) +#define PACKET_MAX_SECTORS (PACKET_MAX_SIZE * CD_FRAMESIZE >> 9) + +enum packet_data_state { + PACKET_IDLE_STATE, /* Not used at the moment */ + PACKET_WAITING_STATE, /* Waiting for more bios to arrive, so */ + /* we don't have to do as much */ + /* data gathering */ + PACKET_READ_WAIT_STATE, /* Waiting for reads to fill in holes */ + PACKET_WRITE_WAIT_STATE, /* Waiting for the write to complete */ + PACKET_RECOVERY_STATE, /* Recover after read/write errors */ + PACKET_FINISHED_STATE, /* After write has finished */ + + PACKET_NUM_STATES /* Number of possible states */ +}; + +/* + * Information needed for writing a single packet + */ +struct pktcdvd_device; + +struct packet_data +{ + struct list_head list; + + spinlock_t lock; /* Lock protecting state transitions and */ + /* orig_bios list */ + + struct bio *orig_bios; /* Original bios passed to pkt_make_request */ + struct bio *orig_bios_tail;/* that will be handled by this packet */ + int write_size; /* Total size of all bios in the orig_bios */ + /* list, measured in number of frames */ + + struct bio *w_bio; /* The bio we will send to the real CD */ + /* device once we have all data for the */ + /* packet we are going to write */ + sector_t sector; /* First sector in this packet */ + int frames; /* Number of frames in this packet */ + + enum packet_data_state state; /* Current state */ + atomic_t run_sm; /* Incremented whenever the state */ + /* machine needs to be run */ + long sleep_time; /* Set this to non-zero to make the state */ + /* machine run after this many jiffies. */ + + atomic_t io_wait; /* Number of pending IO operations */ + atomic_t io_errors; /* Number of read/write errors during IO */ + + struct bio *r_bios[PACKET_MAX_SIZE]; /* bios to use during data gathering */ + struct page *pages[PAGES_PER_PACKET]; + + int cache_valid; /* If non-zero, the data for the zone defined */ + /* by the sector variable is completely cached */ + /* in the pages[] vector. */ + + int id; /* ID number for debugging */ + struct pktcdvd_device *pd; +}; + +struct pkt_rb_node { + struct rb_node rb_node; + struct bio *bio; +}; + +struct packet_stacked_data +{ + struct bio *bio; /* Original read request bio */ + struct pktcdvd_device *pd; +}; +#define PSD_POOL_SIZE 64 + +struct pktcdvd_device +{ + struct block_device *bdev; /* dev attached */ + dev_t pkt_dev; /* our dev */ + char name[20]; + struct packet_settings settings; + struct packet_stats stats; + int refcnt; /* Open count */ + int write_speed; /* current write speed, kB/s */ + int read_speed; /* current read speed, kB/s */ + unsigned long offset; /* start offset */ + __u8 mode_offset; /* 0 / 8 */ + __u8 type; + unsigned long flags; + __u16 mmc3_profile; + __u32 nwa; /* next writable address */ + __u32 lra; /* last recorded address */ + struct packet_cdrw cdrw; + wait_queue_head_t wqueue; + + spinlock_t lock; /* Serialize access to bio_queue */ + struct rb_root bio_queue; /* Work queue of bios we need to handle */ + int bio_queue_size; /* Number of nodes in bio_queue */ + sector_t current_sector; /* Keep track of where the elevator is */ + atomic_t scan_queue; /* Set to non-zero when pkt_handle_queue */ + /* needs to be run. */ + mempool_t *rb_pool; /* mempool for pkt_rb_node allocations */ + + struct packet_iosched iosched; + struct gendisk *disk; +}; + +#endif /* __KERNEL__ */ + +#endif /* __PKTCDVD_H */ -- cgit v1.2.3 From 66d5cab9f5225a0fe501305016129110d580d688 Mon Sep 17 00:00:00 2001 From: Peter Osterlund Date: Mon, 18 Oct 2004 17:57:46 -0700 Subject: [PATCH] cdrom: buffer sizing fix The problem is that some drives fail the "GET CONFIGURATION" command when asked to only return 8 bytes. This happens for example on my drive, which is identified as: hdc: HL-DT-ST DVD+RW GCA-4040N, ATAPI CD/DVD-ROM drive Since the cdrom_mmc3_profile() function already allocates 32 bytes for the reply buffer, this patch is enough to make the command succeed on my drive. Signed-off-by: Peter Osterlund Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/cdrom/cdrom.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/cdrom/cdrom.c b/drivers/cdrom/cdrom.c index 3da410b2c940..4153153aeaf5 100644 --- a/drivers/cdrom/cdrom.c +++ b/drivers/cdrom/cdrom.c @@ -865,7 +865,7 @@ static void cdrom_mmc3_profile(struct cdrom_device_info *cdi) cgc.cmd[0] = GPCMD_GET_CONFIGURATION; cgc.cmd[1] = 0; cgc.cmd[2] = cgc.cmd[3] = 0; /* Starting Feature Number */ - cgc.cmd[7] = 0; cgc.cmd [8] = 8; /* Allocation Length */ + cgc.cmd[8] = sizeof(buffer); /* Allocation Length */ cgc.quiet = 1; if ((ret = cdi->ops->generic_packet(cdi, &cgc))) { -- cgit v1.2.3 From 593564660d9da4ebe34bb6352c68ddfca8d73fa1 Mon Sep 17 00:00:00 2001 From: Matthew Dobson Date: Mon, 18 Oct 2004 17:58:00 -0700 Subject: [PATCH] Create nodemask_t The idea behind this patch is to create a nodemask_t as a node analog of cpumask_t. As NUMA machines become more common, the need for a standard, cross-platform bitmap of both online & possible nodes becomes more apparent. We believe we've worked out most of the kinks of the variable length bitmap types with the recent cpumask_t patches. Nodemasks are also currently far less widespread than cpumasks. Further, inclusion at this point in the kernel would mean consistency in node handling between 2.6 and 2.7. Future goals would be to get rid of the 'numnodes' variable used to count the number of online nodes, and replace with node_online_map. This would allow arbitrary node numbering and facilitate node hotplugging. (Nothing actually uses this yet, but several projects need it, and it does model a well-defined physical grouping). Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- arch/i386/kernel/numaq.c | 1 + arch/i386/kernel/srat.c | 1 + arch/i386/mach-default/topology.c | 1 + arch/i386/mm/discontig.c | 1 + arch/ia64/kernel/acpi.c | 1 + arch/ia64/mm/discontig.c | 1 + arch/ppc64/kernel/sysfs.c | 2 + arch/ppc64/mm/numa.c | 1 + arch/x86_64/mm/k8topology.c | 1 + arch/x86_64/mm/numa.c | 6 +- include/asm-i386/cpu.h | 1 + include/asm-i386/node.h | 1 + include/asm-x86_64/numa.h | 2 + include/linux/mmzone.h | 29 ---- include/linux/nodemask.h | 326 ++++++++++++++++++++++++++++++++++++++ mm/mempolicy.c | 10 +- mm/page_alloc.c | 4 +- 17 files changed, 353 insertions(+), 36 deletions(-) create mode 100644 include/linux/nodemask.h diff --git a/arch/i386/kernel/numaq.c b/arch/i386/kernel/numaq.c index ed41eebf09e8..38c762daba5a 100644 --- a/arch/i386/kernel/numaq.c +++ b/arch/i386/kernel/numaq.c @@ -28,6 +28,7 @@ #include #include #include +#include #include /* These are needed before the pgdat's are created */ diff --git a/arch/i386/kernel/srat.c b/arch/i386/kernel/srat.c index e80cd8e417e0..bb55e0d5187e 100644 --- a/arch/i386/kernel/srat.c +++ b/arch/i386/kernel/srat.c @@ -28,6 +28,7 @@ #include #include #include +#include #include /* diff --git a/arch/i386/mach-default/topology.c b/arch/i386/mach-default/topology.c index c70547a7e81e..37bfa9144c8b 100644 --- a/arch/i386/mach-default/topology.c +++ b/arch/i386/mach-default/topology.c @@ -27,6 +27,7 @@ */ #include #include +#include #include struct i386_cpu cpu_devices[NR_CPUS]; diff --git a/arch/i386/mm/discontig.c b/arch/i386/mm/discontig.c index efdcb0da9ffd..7dace975bae6 100644 --- a/arch/i386/mm/discontig.c +++ b/arch/i386/mm/discontig.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include diff --git a/arch/ia64/kernel/acpi.c b/arch/ia64/kernel/acpi.c index 6e012ba55879..fb010358ab28 100644 --- a/arch/ia64/kernel/acpi.c +++ b/arch/ia64/kernel/acpi.c @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include diff --git a/arch/ia64/mm/discontig.c b/arch/ia64/mm/discontig.c index eb464cbb9319..72e9c16f34ed 100644 --- a/arch/ia64/mm/discontig.c +++ b/arch/ia64/mm/discontig.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include diff --git a/arch/ppc64/kernel/sysfs.c b/arch/ppc64/kernel/sysfs.c index cfc3c0b7b1c2..e7b94ada0e2d 100644 --- a/arch/ppc64/kernel/sysfs.c +++ b/arch/ppc64/kernel/sysfs.c @@ -6,6 +6,8 @@ #include #include #include +#include + #include #include #include diff --git a/arch/ppc64/mm/numa.c b/arch/ppc64/mm/numa.c index 977140f78a37..43e8bc084996 100644 --- a/arch/ppc64/mm/numa.c +++ b/arch/ppc64/mm/numa.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include diff --git a/arch/x86_64/mm/k8topology.c b/arch/x86_64/mm/k8topology.c index 0825e37fd9d8..96cf21236cf2 100644 --- a/arch/x86_64/mm/k8topology.c +++ b/arch/x86_64/mm/k8topology.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include diff --git a/arch/x86_64/mm/numa.c b/arch/x86_64/mm/numa.c index df85a94d3ba6..f136fc23f9cf 100644 --- a/arch/x86_64/mm/numa.c +++ b/arch/x86_64/mm/numa.c @@ -10,6 +10,8 @@ #include #include #include +#include + #include #include #include @@ -151,9 +153,9 @@ void __init numa_init_array(void) for (i = 0; i < MAXNODE; i++) { if (node_online(i)) continue; - rr = find_next_bit(node_online_map, MAX_NUMNODES, rr); + rr = next_node(rr, node_online_map); if (rr == MAX_NUMNODES) - rr = find_first_bit(node_online_map, MAX_NUMNODES); + rr = first_node(node_online_map); node_data[i] = node_data[rr]; cpu_to_node[i] = rr; rr++; diff --git a/include/asm-i386/cpu.h b/include/asm-i386/cpu.h index d962258e2a79..c5d591e4902b 100644 --- a/include/asm-i386/cpu.h +++ b/include/asm-i386/cpu.h @@ -4,6 +4,7 @@ #include #include #include +#include #include diff --git a/include/asm-i386/node.h b/include/asm-i386/node.h index c1aeb27b1354..e13c6ffa72ae 100644 --- a/include/asm-i386/node.h +++ b/include/asm-i386/node.h @@ -5,6 +5,7 @@ #include #include #include +#include struct i386_node { struct node node; diff --git a/include/asm-x86_64/numa.h b/include/asm-x86_64/numa.h index 9962665c3a98..e3ee503a0a21 100644 --- a/include/asm-x86_64/numa.h +++ b/include/asm-x86_64/numa.h @@ -1,6 +1,8 @@ #ifndef _ASM_X8664_NUMA_H #define _ASM_X8664_NUMA_H 1 +#include + #define MAXNODE 8 #define NODEMASK 0xff diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h index 7c36a10f6720..b812151cdd07 100644 --- a/include/linux/mmzone.h +++ b/include/linux/mmzone.h @@ -410,35 +410,6 @@ extern struct pglist_data contig_page_data; #error ZONES_SHIFT > MAX_ZONES_SHIFT #endif -extern DECLARE_BITMAP(node_online_map, MAX_NUMNODES); - -#if defined(CONFIG_DISCONTIGMEM) || defined(CONFIG_NUMA) - -#define node_online(node) test_bit(node, node_online_map) -#define node_set_online(node) set_bit(node, node_online_map) -#define node_set_offline(node) clear_bit(node, node_online_map) -static inline unsigned int num_online_nodes(void) -{ - int i, num = 0; - - for(i = 0; i < MAX_NUMNODES; i++){ - if (node_online(i)) - num++; - } - return num; -} - -#else /* !CONFIG_DISCONTIGMEM && !CONFIG_NUMA */ - -#define node_online(node) \ - ({ BUG_ON((node) != 0); test_bit(node, node_online_map); }) -#define node_set_online(node) \ - ({ BUG_ON((node) != 0); set_bit(node, node_online_map); }) -#define node_set_offline(node) \ - ({ BUG_ON((node) != 0); clear_bit(node, node_online_map); }) -#define num_online_nodes() 1 - -#endif /* CONFIG_DISCONTIGMEM || CONFIG_NUMA */ #endif /* !__ASSEMBLY__ */ #endif /* __KERNEL__ */ #endif /* _LINUX_MMZONE_H */ diff --git a/include/linux/nodemask.h b/include/linux/nodemask.h new file mode 100644 index 000000000000..4de843d94147 --- /dev/null +++ b/include/linux/nodemask.h @@ -0,0 +1,326 @@ +#ifndef __LINUX_NODEMASK_H +#define __LINUX_NODEMASK_H + +/* + * Nodemasks provide a bitmap suitable for representing the + * set of Node's in a system, one bit position per Node number. + * + * See detailed comments in the file linux/bitmap.h describing the + * data type on which these nodemasks are based. + * + * For details of nodemask_scnprintf() and nodemask_parse(), + * see bitmap_scnprintf() and bitmap_parse() in lib/bitmap.c. + * + * The available nodemask operations are: + * + * void node_set(node, mask) turn on bit 'node' in mask + * void node_clear(node, mask) turn off bit 'node' in mask + * void nodes_setall(mask) set all bits + * void nodes_clear(mask) clear all bits + * int node_isset(node, mask) true iff bit 'node' set in mask + * int node_test_and_set(node, mask) test and set bit 'node' in mask + * + * void nodes_and(dst, src1, src2) dst = src1 & src2 [intersection] + * void nodes_or(dst, src1, src2) dst = src1 | src2 [union] + * void nodes_xor(dst, src1, src2) dst = src1 ^ src2 + * void nodes_andnot(dst, src1, src2) dst = src1 & ~src2 + * void nodes_complement(dst, src) dst = ~src + * + * int nodes_equal(mask1, mask2) Does mask1 == mask2? + * int nodes_intersects(mask1, mask2) Do mask1 and mask2 intersect? + * int nodes_subset(mask1, mask2) Is mask1 a subset of mask2? + * int nodes_empty(mask) Is mask empty (no bits sets)? + * int nodes_full(mask) Is mask full (all bits sets)? + * int nodes_weight(mask) Hamming weight - number of set bits + * + * void nodes_shift_right(dst, src, n) Shift right + * void nodes_shift_left(dst, src, n) Shift left + * + * int first_node(mask) Number lowest set bit, or MAX_NUMNODES + * int next_node(node, mask) Next node past 'node', or MAX_NUMNODES + * + * nodemask_t nodemask_of_node(node) Return nodemask with bit 'node' set + * NODE_MASK_ALL Initializer - all bits set + * NODE_MASK_NONE Initializer - no bits set + * unsigned long *nodes_addr(mask) Array of unsigned long's in mask + * + * int nodemask_scnprintf(buf, len, mask) Format nodemask for printing + * int nodemask_parse(ubuf, ulen, mask) Parse ascii string as nodemask + * + * for_each_node_mask(node, mask) for-loop node over mask + * + * int num_online_nodes() Number of online Nodes + * int num_possible_nodes() Number of all possible Nodes + * + * int node_online(node) Is some node online? + * int node_possible(node) Is some node possible? + * + * int any_online_node(mask) First online node in mask + * + * node_set_online(node) set bit 'node' in node_online_map + * node_set_offline(node) clear bit 'node' in node_online_map + * + * for_each_node(node) for-loop node over node_possible_map + * for_each_online_node(node) for-loop node over node_online_map + * + * Subtlety: + * 1) The 'type-checked' form of node_isset() causes gcc (3.3.2, anyway) + * to generate slightly worse code. So use a simple one-line #define + * for node_isset(), instead of wrapping an inline inside a macro, the + * way we do the other calls. + */ + +#include +#include +#include +#include +#include + +typedef struct { DECLARE_BITMAP(bits, MAX_NUMNODES); } nodemask_t; +extern nodemask_t _unused_nodemask_arg_; + +#define node_set(node, dst) __node_set((node), &(dst)) +static inline void __node_set(int node, volatile nodemask_t *dstp) +{ + set_bit(node, dstp->bits); +} + +#define node_clear(node, dst) __node_clear((node), &(dst)) +static inline void __node_clear(int node, volatile nodemask_t *dstp) +{ + clear_bit(node, dstp->bits); +} + +#define nodes_setall(dst) __nodes_setall(&(dst), MAX_NUMNODES) +static inline void __nodes_setall(nodemask_t *dstp, int nbits) +{ + bitmap_fill(dstp->bits, nbits); +} + +#define nodes_clear(dst) __nodes_clear(&(dst), MAX_NUMNODES) +static inline void __nodes_clear(nodemask_t *dstp, int nbits) +{ + bitmap_zero(dstp->bits, nbits); +} + +/* No static inline type checking - see Subtlety (1) above. */ +#define node_isset(node, nodemask) test_bit((node), (nodemask).bits) + +#define node_test_and_set(node, nodemask) \ + __node_test_and_set((node), &(nodemask)) +static inline int __node_test_and_set(int node, nodemask_t *addr) +{ + return test_and_set_bit(node, addr->bits); +} + +#define nodes_and(dst, src1, src2) \ + __nodes_and(&(dst), &(src1), &(src2), MAX_NUMNODES) +static inline void __nodes_and(nodemask_t *dstp, const nodemask_t *src1p, + const nodemask_t *src2p, int nbits) +{ + bitmap_and(dstp->bits, src1p->bits, src2p->bits, nbits); +} + +#define nodes_or(dst, src1, src2) \ + __nodes_or(&(dst), &(src1), &(src2), MAX_NUMNODES) +static inline void __nodes_or(nodemask_t *dstp, const nodemask_t *src1p, + const nodemask_t *src2p, int nbits) +{ + bitmap_or(dstp->bits, src1p->bits, src2p->bits, nbits); +} + +#define nodes_xor(dst, src1, src2) \ + __nodes_xor(&(dst), &(src1), &(src2), MAX_NUMNODES) +static inline void __nodes_xor(nodemask_t *dstp, const nodemask_t *src1p, + const nodemask_t *src2p, int nbits) +{ + bitmap_xor(dstp->bits, src1p->bits, src2p->bits, nbits); +} + +#define nodes_andnot(dst, src1, src2) \ + __nodes_andnot(&(dst), &(src1), &(src2), MAX_NUMNODES) +static inline void __nodes_andnot(nodemask_t *dstp, const nodemask_t *src1p, + const nodemask_t *src2p, int nbits) +{ + bitmap_andnot(dstp->bits, src1p->bits, src2p->bits, nbits); +} + +#define nodes_complement(dst, src) \ + __nodes_complement(&(dst), &(src), MAX_NUMNODES) +static inline void __nodes_complement(nodemask_t *dstp, + const nodemask_t *srcp, int nbits) +{ + bitmap_complement(dstp->bits, srcp->bits, nbits); +} + +#define nodes_equal(src1, src2) \ + __nodes_equal(&(src1), &(src2), MAX_NUMNODES) +static inline int __nodes_equal(const nodemask_t *src1p, + const nodemask_t *src2p, int nbits) +{ + return bitmap_equal(src1p->bits, src2p->bits, nbits); +} + +#define nodes_intersects(src1, src2) \ + __nodes_intersects(&(src1), &(src2), MAX_NUMNODES) +static inline int __nodes_intersects(const nodemask_t *src1p, + const nodemask_t *src2p, int nbits) +{ + return bitmap_intersects(src1p->bits, src2p->bits, nbits); +} + +#define nodes_subset(src1, src2) \ + __nodes_subset(&(src1), &(src2), MAX_NUMNODES) +static inline int __nodes_subset(const nodemask_t *src1p, + const nodemask_t *src2p, int nbits) +{ + return bitmap_subset(src1p->bits, src2p->bits, nbits); +} + +#define nodes_empty(src) __nodes_empty(&(src), MAX_NUMNODES) +static inline int __nodes_empty(const nodemask_t *srcp, int nbits) +{ + return bitmap_empty(srcp->bits, nbits); +} + +#define nodes_full(nodemask) __nodes_full(&(nodemask), MAX_NUMNODES) +static inline int __nodes_full(const nodemask_t *srcp, int nbits) +{ + return bitmap_full(srcp->bits, nbits); +} + +#define nodes_weight(nodemask) __nodes_weight(&(nodemask), MAX_NUMNODES) +static inline int __nodes_weight(const nodemask_t *srcp, int nbits) +{ + return bitmap_weight(srcp->bits, nbits); +} + +#define nodes_shift_right(dst, src, n) \ + __nodes_shift_right(&(dst), &(src), (n), MAX_NUMNODES) +static inline void __nodes_shift_right(nodemask_t *dstp, + const nodemask_t *srcp, int n, int nbits) +{ + bitmap_shift_right(dstp->bits, srcp->bits, n, nbits); +} + +#define nodes_shift_left(dst, src, n) \ + __nodes_shift_left(&(dst), &(src), (n), MAX_NUMNODES) +static inline void __nodes_shift_left(nodemask_t *dstp, + const nodemask_t *srcp, int n, int nbits) +{ + bitmap_shift_left(dstp->bits, srcp->bits, n, nbits); +} + +#define first_node(src) __first_node(&(src), MAX_NUMNODES) +static inline int __first_node(const nodemask_t *srcp, int nbits) +{ + return min_t(int, nbits, find_first_bit(srcp->bits, nbits)); +} + +#define next_node(n, src) __next_node((n), &(src), MAX_NUMNODES) +static inline int __next_node(int n, const nodemask_t *srcp, int nbits) +{ + return min_t(int, nbits, find_next_bit(srcp->bits, nbits, n+1)); +} + +#define nodemask_of_node(node) \ +({ \ + typeof(_unused_nodemask_arg_) m; \ + if (sizeof(m) == sizeof(unsigned long)) { \ + m.bits[0] = 1UL<<(node); \ + } else { \ + nodes_clear(m); \ + node_set((node), m); \ + } \ + m; \ +}) + +#define NODE_MASK_LAST_WORD BITMAP_LAST_WORD_MASK(MAX_NUMNODES) + +#if MAX_NUMNODES <= BITS_PER_LONG + +#define NODE_MASK_ALL \ +((nodemask_t) { { \ + [BITS_TO_LONGS(MAX_NUMNODES)-1] = NODE_MASK_LAST_WORD \ +} }) + +#else + +#define NODE_MASK_ALL \ +((nodemask_t) { { \ + [0 ... BITS_TO_LONGS(MAX_NUMNODES)-2] = ~0UL, \ + [BITS_TO_LONGS(MAX_NUMNODES)-1] = NODE_MASK_LAST_WORD \ +} }) + +#endif + +#define NODE_MASK_NONE \ +((nodemask_t) { { \ + [0 ... BITS_TO_LONGS(MAX_NUMNODES)-1] = 0UL \ +} }) + +#define nodes_addr(src) ((src).bits) + +#define nodemask_scnprintf(buf, len, src) \ + __nodemask_scnprintf((buf), (len), &(src), MAX_NUMNODES) +static inline int __nodemask_scnprintf(char *buf, int len, + const nodemask_t *srcp, int nbits) +{ + return bitmap_scnprintf(buf, len, srcp->bits, nbits); +} + +#define nodemask_parse(ubuf, ulen, src) \ + __nodemask_parse((ubuf), (ulen), &(src), MAX_NUMNODES) +static inline int __nodemask_parse(const char __user *buf, int len, + nodemask_t *dstp, int nbits) +{ + return bitmap_parse(buf, len, dstp->bits, nbits); +} + +#if MAX_NUMNODES > 1 +#define for_each_node_mask(node, mask) \ + for ((node) = first_node(mask); \ + (node) < MAX_NUMNODES; \ + (node) = next_node((node), (mask))) +#else /* MAX_NUMNODES == 1 */ +#define for_each_node_mask(node, mask) \ + if (!nodes_empty(mask)) \ + for ((node) = 0; (node) < 1; (node)++) +#endif /* MAX_NUMNODES */ + +/* + * The following particular system nodemasks and operations + * on them manage all possible and online nodes. + */ + +extern nodemask_t node_online_map; +extern nodemask_t node_possible_map; + +#if MAX_NUMNODES > 1 +#define num_online_nodes() nodes_weight(node_online_map) +#define num_possible_nodes() nodes_weight(node_possible_map) +#define node_online(node) node_isset((node), node_online_map) +#define node_possible(node) node_isset((node), node_possible_map) +#else +#define num_online_nodes() 1 +#define num_possible_nodes() 1 +#define node_online(node) ((node) == 0) +#define node_possible(node) ((node) == 0) +#endif + +#define any_online_node(mask) \ +({ \ + int node; \ + for_each_node_mask(node, (mask)) \ + if (node_online(node)) \ + break; \ + node; \ +}) + +#define node_set_online(node) set_bit((node), node_online_map.bits) +#define node_set_offline(node) clear_bit((node), node_online_map.bits) + +#define for_each_node(node) for_each_node_mask((node), node_possible_map) +#define for_each_online_node(node) for_each_node_mask((node), node_online_map) + +#endif /* __LINUX_NODEMASK_H */ diff --git a/mm/mempolicy.c b/mm/mempolicy.c index 8fe9c7ee9853..9eac9c971104 100644 --- a/mm/mempolicy.c +++ b/mm/mempolicy.c @@ -66,6 +66,7 @@ #include #include #include +#include #include #include #include @@ -95,7 +96,7 @@ static int nodes_online(unsigned long *nodes) { DECLARE_BITMAP(online2, MAX_NUMNODES); - bitmap_copy(online2, node_online_map, MAX_NUMNODES); + bitmap_copy(online2, nodes_addr(node_online_map), MAX_NUMNODES); if (bitmap_empty(online2, MAX_NUMNODES)) set_bit(0, online2); if (!bitmap_subset(nodes, online2, MAX_NUMNODES)) @@ -424,7 +425,7 @@ static void get_zonemask(struct mempolicy *p, unsigned long *nodes) case MPOL_PREFERRED: /* or use current node instead of online map? */ if (p->v.preferred_node < 0) - bitmap_copy(nodes, node_online_map, MAX_NUMNODES); + bitmap_copy(nodes, nodes_addr(node_online_map), MAX_NUMNODES); else __set_bit(p->v.preferred_node, nodes); break; @@ -692,7 +693,7 @@ static struct page *alloc_page_interleave(unsigned gfp, unsigned order, unsigned struct zonelist *zl; struct page *page; - BUG_ON(!test_bit(nid, node_online_map)); + BUG_ON(!node_online(nid)); zl = NODE_DATA(nid)->node_zonelists + (gfp & GFP_ZONEMASK); page = __alloc_pages(gfp, order, zl); if (page && page_zone(page) == zl->zones[0]) { @@ -1081,7 +1082,8 @@ void __init numa_policy_init(void) /* Set interleaving policy for system init. This way not all the data structures allocated at system boot end up in node zero. */ - if (sys_set_mempolicy(MPOL_INTERLEAVE, node_online_map, MAX_NUMNODES) < 0) + if (sys_set_mempolicy(MPOL_INTERLEAVE, nodes_addr(node_online_map), + MAX_NUMNODES) < 0) printk("numa_policy_init: interleaving failed\n"); } diff --git a/mm/page_alloc.c b/mm/page_alloc.c index f2b5f575a410..bd4e102ee22e 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -31,10 +31,12 @@ #include #include #include +#include #include -DECLARE_BITMAP(node_online_map, MAX_NUMNODES); +nodemask_t node_online_map = NODE_MASK_NONE; +nodemask_t node_possible_map = NODE_MASK_ALL; struct pglist_data *pgdat_list; unsigned long totalram_pages; unsigned long totalhigh_pages; -- cgit v1.2.3 From 6f1afa771c8c4260aa040bf028073c61d62ccc85 Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Mon, 18 Oct 2004 17:58:13 -0700 Subject: [PATCH] reiserfs: rename struct key Rename resierfs's `struct key' to `struct reiserfs_key' to avoid namespace clashes. Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/reiserfs/bitmap.c | 4 ++-- fs/reiserfs/dir.c | 4 ++-- fs/reiserfs/fix_node.c | 4 ++-- fs/reiserfs/ibalance.c | 8 ++++---- fs/reiserfs/item_ops.c | 10 +++++----- fs/reiserfs/namei.c | 2 +- fs/reiserfs/prints.c | 10 +++++----- fs/reiserfs/stree.c | 28 +++++++++++++------------- fs/reiserfs/super.c | 8 ++++---- include/linux/reiserfs_fs.h | 48 ++++++++++++++++++++++----------------------- 10 files changed, 63 insertions(+), 63 deletions(-) diff --git a/fs/reiserfs/bitmap.c b/fs/reiserfs/bitmap.c index cdc2a91609d7..c9f8e2feef10 100644 --- a/fs/reiserfs/bitmap.c +++ b/fs/reiserfs/bitmap.c @@ -736,7 +736,7 @@ static inline int this_blocknr_allocation_would_make_it_a_large_file(reiserfs_bl #ifdef DISPLACE_NEW_PACKING_LOCALITIES static inline void displace_new_packing_locality (reiserfs_blocknr_hint_t *hint) { - struct key * key = &hint->key; + struct reiserfs_key * key = &hint->key; hint->th->displace_new_blocks = 0; hint->search_start = hint->beg + keyed_hash((char*)(&key->k_objectid),4) % (hint->end - hint->beg); @@ -777,7 +777,7 @@ static inline int old_way (reiserfs_blocknr_hint_t * hint) static inline void hundredth_slices (reiserfs_blocknr_hint_t * hint) { - struct key * key = &hint->key; + struct reiserfs_key * key = &hint->key; b_blocknr_t slice_start; slice_start = (keyed_hash((char*)(&key->k_dir_id),4) % 100) * (hint->end / 100); diff --git a/fs/reiserfs/dir.c b/fs/reiserfs/dir.c index c222a92b5a94..268474f46830 100644 --- a/fs/reiserfs/dir.c +++ b/fs/reiserfs/dir.c @@ -12,7 +12,7 @@ #include #include -extern struct key MIN_KEY; +extern struct reiserfs_key MIN_KEY; static int reiserfs_readdir (struct file *, void *, filldir_t); int reiserfs_dir_fsync(struct file *filp, struct dentry *dentry, int datasync) ; @@ -46,7 +46,7 @@ static int reiserfs_readdir (struct file * filp, void * dirent, filldir_t filldi INITIALIZE_PATH (path_to_entry); struct buffer_head * bh; int item_num, entry_num; - const struct key * rkey; + const struct reiserfs_key * rkey; struct item_head * ih, tmp_ih; int search_res; char * local_buf; diff --git a/fs/reiserfs/fix_node.c b/fs/reiserfs/fix_node.c index f038aa7075b2..814561b10324 100644 --- a/fs/reiserfs/fix_node.c +++ b/fs/reiserfs/fix_node.c @@ -163,7 +163,7 @@ static void create_virtual_node (struct tree_balance * tb, int h) /* set right merge flag we take right delimiting key and check whether it is a mergeable item */ if (tb->CFR[0]) { - struct key * key; + struct reiserfs_key * key; key = B_N_PDELIM_KEY (tb->CFR[0], tb->rkey[0]); if (op_is_left_mergeable (key, Sh->b_size) && (vn->vn_mode != M_DELETE || @@ -1140,7 +1140,7 @@ static inline int can_node_be_removed (int mode, int lfree, int sfree, int rfree struct buffer_head * Sh = PATH_H_PBUFFER (tb->tb_path, h); int levbytes = tb->insert_size[h]; struct item_head * ih; - struct key * r_key = NULL; + struct reiserfs_key * r_key = NULL; ih = B_N_PITEM_HEAD (Sh, 0); if ( tb->CFR[h] ) diff --git a/fs/reiserfs/ibalance.c b/fs/reiserfs/ibalance.c index 5b9dee2ac2ae..d1033e3f89ea 100644 --- a/fs/reiserfs/ibalance.c +++ b/fs/reiserfs/ibalance.c @@ -133,7 +133,7 @@ static void internal_insert_childs (struct buffer_info * cur_bi, struct buffer_head * cur = cur_bi->bi_bh; struct block_head * blkh; int nr; - struct key * ih; + struct reiserfs_key * ih; struct disk_child new_dc[2]; struct disk_child * dc; int i; @@ -209,7 +209,7 @@ static void internal_delete_pointers_items ( struct buffer_head * cur = cur_bi->bi_bh; int nr; struct block_head * blkh; - struct key * key; + struct reiserfs_key * key; struct disk_child * dc; RFALSE( cur == NULL, "buffer is 0"); @@ -300,7 +300,7 @@ static void internal_copy_pointers_items ( int nr_dest, nr_src; int dest_order, src_order; struct block_head * blkh; - struct key * key; + struct reiserfs_key * key; struct disk_child * dc; nr_src = B_NR_ITEMS (src); @@ -409,7 +409,7 @@ static void internal_insert_key (struct buffer_info * dest_bi, struct buffer_head * dest = dest_bi->bi_bh; int nr; struct block_head * blkh; - struct key * key; + struct reiserfs_key * key; RFALSE( dest == NULL || src == NULL, "source(%p) or dest(%p) buffer is 0", src, dest); diff --git a/fs/reiserfs/item_ops.c b/fs/reiserfs/item_ops.c index c315edbb2187..57779620a626 100644 --- a/fs/reiserfs/item_ops.c +++ b/fs/reiserfs/item_ops.c @@ -26,7 +26,7 @@ static void sd_decrement_key (struct cpu_key * key) set_cpu_key_k_offset(key, (loff_t)(-1)); } -static int sd_is_left_mergeable (struct key * key, unsigned long bsize) +static int sd_is_left_mergeable (struct reiserfs_key * key, unsigned long bsize) { return 0; } @@ -145,7 +145,7 @@ static void direct_decrement_key (struct cpu_key * key) } -static int direct_is_left_mergeable (struct key * key, unsigned long bsize) +static int direct_is_left_mergeable (struct reiserfs_key * key, unsigned long bsize) { int version = le_key_version (key); return ((le_key_k_offset (version, key) & (bsize - 1)) != 1); @@ -250,7 +250,7 @@ static void indirect_decrement_key (struct cpu_key * key) // if it is not first item of the body, then it is mergeable -static int indirect_is_left_mergeable (struct key * key, unsigned long bsize) +static int indirect_is_left_mergeable (struct reiserfs_key * key, unsigned long bsize) { int version = le_key_version (key); return (le_key_k_offset (version, key) != 1); @@ -403,7 +403,7 @@ static void direntry_decrement_key (struct cpu_key * key) } -static int direntry_is_left_mergeable (struct key * key, unsigned long bsize) +static int direntry_is_left_mergeable (struct reiserfs_key * key, unsigned long bsize) { if (le32_to_cpu (key->u.k_offset_v1.k_offset) == DOT_OFFSET) return 0; @@ -691,7 +691,7 @@ static void errcatch_decrement_key (struct cpu_key * key) } -static int errcatch_is_left_mergeable (struct key * key, unsigned long bsize) +static int errcatch_is_left_mergeable (struct reiserfs_key * key, unsigned long bsize) { reiserfs_warning (NULL, "green-16003: Invalid item type observed, run fsck ASAP"); return 0; diff --git a/fs/reiserfs/namei.c b/fs/reiserfs/namei.c index e12fcdff5a69..30e19a145fa7 100644 --- a/fs/reiserfs/namei.c +++ b/fs/reiserfs/namei.c @@ -1166,7 +1166,7 @@ static int entry_points_to_object (const char * name, int len, struct reiserfs_d /* sets key of objectid the entry has to point to */ -static void set_ino_in_dir_entry (struct reiserfs_dir_entry * de, struct key * key) +static void set_ino_in_dir_entry (struct reiserfs_dir_entry * de, struct reiserfs_key * key) { /* JDM These operations are endian safe - both are le */ de->de_deh[de->de_entry_num].deh_dir_id = key->k_dir_id; diff --git a/fs/reiserfs/prints.c b/fs/reiserfs/prints.c index 333ad7c001b3..5f08f356da1a 100644 --- a/fs/reiserfs/prints.c +++ b/fs/reiserfs/prints.c @@ -28,7 +28,7 @@ static char * reiserfs_cpu_offset (struct cpu_key * key) } -static char * le_offset (struct key * key) +static char * le_offset (struct reiserfs_key * key) { int version; @@ -57,7 +57,7 @@ static char * cpu_type (struct cpu_key * key) } -static char * le_type (struct key * key) +static char * le_type (struct reiserfs_key * key) { int version; @@ -76,7 +76,7 @@ static char * le_type (struct key * key) /* %k */ -static void sprintf_le_key (char * buf, struct key * key) +static void sprintf_le_key (char * buf, struct reiserfs_key * key) { if (key) sprintf (buf, "[%d %d %s %s]", le32_to_cpu (key->k_dir_id), @@ -213,7 +213,7 @@ prepare_error_buf( const char *fmt, va_list args ) switch (what) { case 'k': - sprintf_le_key (p, va_arg(args, struct key *)); + sprintf_le_key (p, va_arg(args, struct reiserfs_key *)); break; case 'K': sprintf_cpu_key (p, va_arg(args, struct cpu_key *)); @@ -462,7 +462,7 @@ void print_path (struct tree_balance * tb, struct path * path) dc_size)...*/ static int print_internal (struct buffer_head * bh, int first, int last) { - struct key * key; + struct reiserfs_key * key; struct disk_child * dc; int i; int from, to; diff --git a/fs/reiserfs/stree.c b/fs/reiserfs/stree.c index eae0b197fd34..caea0377b668 100644 --- a/fs/reiserfs/stree.c +++ b/fs/reiserfs/stree.c @@ -93,7 +93,7 @@ inline void copy_item_head(struct item_head * p_v_to, Returns: -1 if key1 < key2 0 if key1 == key2 1 if key1 > key2 */ -inline int comp_short_keys (const struct key * le_key, +inline int comp_short_keys (const struct reiserfs_key * le_key, const struct cpu_key * cpu_key) { __u32 * p_s_le_u32, * p_s_cpu_u32; @@ -117,7 +117,7 @@ inline int comp_short_keys (const struct key * le_key, Compare keys using all 4 key fields. Returns: -1 if key1 < key2 0 if key1 = key2 1 if key1 > key2 */ -inline int comp_keys (const struct key * le_key, const struct cpu_key * cpu_key) +inline int comp_keys (const struct reiserfs_key * le_key, const struct cpu_key * cpu_key) { int retval; @@ -174,7 +174,7 @@ inline int comp_cpu_keys (const struct cpu_key * key1, return 0; } -inline int comp_short_le_keys (const struct key * key1, const struct key * key2) +inline int comp_short_le_keys (const struct reiserfs_key * key1, const struct reiserfs_key * key2) { __u32 * p_s_1_u32, * p_s_2_u32; int n_key_length = REISERFS_SHORT_KEY_LEN; @@ -216,7 +216,7 @@ inline void cpu_key2cpu_key (struct cpu_key * to, const struct cpu_key * from) } -inline void le_key2cpu_key (struct cpu_key * to, const struct key * from) +inline void le_key2cpu_key (struct cpu_key * to, const struct reiserfs_key * from) { to->on_disk_key.k_dir_id = le32_to_cpu (from->k_dir_id); to->on_disk_key.k_objectid = le32_to_cpu (from->k_objectid); @@ -236,9 +236,9 @@ inline void le_key2cpu_key (struct cpu_key * to, const struct key * from) // this does not say which one is bigger, it only returns 1 if keys // are not equal, 0 otherwise -inline int comp_le_keys (const struct key * k1, const struct key * k2) +inline int comp_le_keys (const struct reiserfs_key * k1, const struct reiserfs_key * k2) { - return memcmp (k1, k2, sizeof (struct key)); + return memcmp (k1, k2, sizeof (struct reiserfs_key)); } /************************************************************************** @@ -272,7 +272,7 @@ inline int bin_search ( int n_rbound, n_lbound, n_j; for ( n_j = ((n_rbound = p_n_num - 1) + (n_lbound = 0))/2; n_lbound <= n_rbound; n_j = (n_rbound + n_lbound)/2 ) - switch( COMP_KEYS((struct key *)((char * )p_v_base + n_j * p_n_width), (struct cpu_key *)p_v_key) ) { + switch( COMP_KEYS((struct reiserfs_key *)((char * )p_v_base + n_j * p_n_width), (struct cpu_key *)p_v_key) ) { case -1: n_lbound = n_j + 1; continue; case 1: n_rbound = n_j - 1; continue; case 0: *p_n_pos = n_j; return ITEM_FOUND; /* Key found in the array. */ @@ -291,17 +291,17 @@ extern struct tree_balance * cur_tb; /* Minimal possible key. It is never in the tree. */ -const struct key MIN_KEY = {0, 0, {{0, 0},}}; +const struct reiserfs_key MIN_KEY = {0, 0, {{0, 0},}}; /* Maximal possible key. It is never in the tree. */ -const struct key MAX_KEY = {0xffffffff, 0xffffffff, {{0xffffffff, 0xffffffff},}}; +const struct reiserfs_key MAX_KEY = {0xffffffff, 0xffffffff, {{0xffffffff, 0xffffffff},}}; /* Get delimiting key of the buffer by looking for it in the buffers in the path, starting from the bottom of the path, and going upwards. We must check the path's validity at each step. If the key is not in the path, there is no delimiting key in the tree (buffer is first or last buffer in tree), and in this case we return a special key, either MIN_KEY or MAX_KEY. */ -inline const struct key * get_lkey ( +inline const struct reiserfs_key * get_lkey ( const struct path * p_s_chk_path, const struct super_block * p_s_sb ) { @@ -340,7 +340,7 @@ inline const struct key * get_lkey ( /* Get delimiting key of the buffer at the path and its right neighbor. */ -inline const struct key * get_rkey ( +inline const struct reiserfs_key * get_rkey ( const struct path * p_s_chk_path, const struct super_block * p_s_sb ) { @@ -802,7 +802,7 @@ io_error: { int pos = p_s_last_element->pe_position; int limit = B_NR_ITEMS(p_s_bh); - struct key *le_key; + struct reiserfs_key *le_key; if (p_s_search_path->reada & PATH_READA_BACK) limit = 0; @@ -1247,7 +1247,7 @@ void padd_item (char * item, int total_length, int length) } #ifdef REISERQUOTA_DEBUG -char key2type(struct key *ih) +char key2type(struct reiserfs_key *ih) { if (is_direntry_le_key(2, ih)) return 'd'; @@ -1417,7 +1417,7 @@ int reiserfs_delete_item (struct reiserfs_transaction_handle *th, /* this deletes item which never gets split */ void reiserfs_delete_solid_item (struct reiserfs_transaction_handle *th, struct inode *inode, - struct key * key) + struct reiserfs_key * key) { struct tree_balance tb; INITIALIZE_PATH (path); diff --git a/fs/reiserfs/super.c b/fs/reiserfs/super.c index 23dd560a38aa..3e7d7e0845e8 100644 --- a/fs/reiserfs/super.c +++ b/fs/reiserfs/super.c @@ -106,7 +106,7 @@ void reiserfs_unlockfs(struct super_block *s) { reiserfs_allow_writes(s) ; } -extern const struct key MAX_KEY; +extern const struct reiserfs_key MAX_KEY; /* this is used to delete "save link" when there are no items of a @@ -116,7 +116,7 @@ extern const struct key MAX_KEY; protecting unlink is bigger that a key lf "save link" which protects truncate), so there left no items to make truncate completion on */ -static int remove_save_link_only (struct super_block * s, struct key * key, int oid_free) +static int remove_save_link_only (struct super_block * s, struct reiserfs_key * key, int oid_free) { struct reiserfs_transaction_handle th; int err; @@ -140,7 +140,7 @@ static int finish_unfinished (struct super_block * s) { INITIALIZE_PATH (path); struct cpu_key max_cpu_key, obj_key; - struct key save_link_key; + struct reiserfs_key save_link_key; int retval = 0; struct item_head * ih; struct buffer_head * bh; @@ -335,7 +335,7 @@ void add_save_link (struct reiserfs_transaction_handle * th, int remove_save_link (struct inode * inode, int truncate) { struct reiserfs_transaction_handle th; - struct key key; + struct reiserfs_key key; int err; /* we are going to do one balancing only */ diff --git a/include/linux/reiserfs_fs.h b/include/linux/reiserfs_fs.h index 6eacc2c653f1..df62d2ac38a7 100644 --- a/include/linux/reiserfs_fs.h +++ b/include/linux/reiserfs_fs.h @@ -438,7 +438,7 @@ static inline void set_offset_v2_k_offset( struct offset_v2 *v2, loff_t offset ) /* Key of an item determines its location in the S+tree, and is composed of 4 components */ -struct key { +struct reiserfs_key { __u32 k_dir_id; /* packing locality: by default parent directory object id */ __u32 k_objectid; /* object identifier */ @@ -450,7 +450,7 @@ struct key { struct cpu_key { - struct key on_disk_key; + struct reiserfs_key on_disk_key; int version; int key_length; /* 3 in all cases but direct2indirect and indirect2direct conversion */ @@ -470,7 +470,7 @@ struct cpu_key { #define KEY_FOUND 1 #define KEY_NOT_FOUND 0 -#define KEY_SIZE (sizeof(struct key)) +#define KEY_SIZE (sizeof(struct reiserfs_key)) #define SHORT_KEY_SIZE (sizeof (__u32) + sizeof (__u32)) /* return values for search_by_key and clones */ @@ -503,7 +503,7 @@ struct item_head { /* Everything in the tree is found by searching for it based on * its key.*/ - struct key ih_key; + struct reiserfs_key ih_key; union { /* The free space in the last unformatted node of an indirect item if this is an indirect item. This @@ -602,7 +602,7 @@ static inline __u32 type2uniqueness (int type) // there is no way to get version of object from key, so, provide // version to these defines // -static inline loff_t le_key_k_offset (int version, const struct key * key) +static inline loff_t le_key_k_offset (int version, const struct reiserfs_key * key) { return (version == KEY_FORMAT_3_5) ? le32_to_cpu( key->u.k_offset_v1.k_offset ) : @@ -614,7 +614,7 @@ static inline loff_t le_ih_k_offset (const struct item_head * ih) return le_key_k_offset (ih_version (ih), &(ih->ih_key)); } -static inline loff_t le_key_k_type (int version, const struct key * key) +static inline loff_t le_key_k_type (int version, const struct reiserfs_key * key) { return (version == KEY_FORMAT_3_5) ? uniqueness2type( le32_to_cpu( key->u.k_offset_v1.k_uniqueness)) : @@ -627,7 +627,7 @@ static inline loff_t le_ih_k_type (const struct item_head * ih) } -static inline void set_le_key_k_offset (int version, struct key * key, loff_t offset) +static inline void set_le_key_k_offset (int version, struct reiserfs_key * key, loff_t offset) { (version == KEY_FORMAT_3_5) ? (void)(key->u.k_offset_v1.k_offset = cpu_to_le32 (offset)) : /* jdm check */ @@ -641,7 +641,7 @@ static inline void set_le_ih_k_offset (struct item_head * ih, loff_t offset) } -static inline void set_le_key_k_type (int version, struct key * key, int type) +static inline void set_le_key_k_type (int version, struct reiserfs_key * key, int type) { (version == KEY_FORMAT_3_5) ? (void)(key->u.k_offset_v1.k_uniqueness = cpu_to_le32(type2uniqueness(type))): @@ -738,7 +738,7 @@ static inline void cpu_key_k_offset_dec (struct cpu_key * key) /* object identifier for root dir */ #define REISERFS_ROOT_OBJECTID 2 #define REISERFS_ROOT_PARENT_OBJECTID 1 -extern struct key root_key; +extern struct reiserfs_key root_key; @@ -760,7 +760,7 @@ struct block_head { __u16 blk_free_space; /* Block free space in bytes. */ __u16 blk_reserved; /* dump this in v4/planA */ - struct key blk_right_delim_key; /* kept only for compatibility */ + struct reiserfs_key blk_right_delim_key; /* kept only for compatibility */ }; #define BLKH_SIZE (sizeof(struct block_head)) @@ -1301,7 +1301,7 @@ struct path var = {.path_length = ILLEGAL_PATH_ELEMENT_OFFSET, .reada = 0,} #define UNFM_P_SHIFT 2 // in in-core inode key is stored on le form -#define INODE_PKEY(inode) ((struct key *)(REISERFS_I(inode)->i_key)) +#define INODE_PKEY(inode) ((struct reiserfs_key *)(REISERFS_I(inode)->i_key)) #define MAX_UL_INT 0xffffffff #define MAX_INT 0x7ffffff @@ -1479,7 +1479,7 @@ struct tree_balance int fs_gen; /* saved value of `reiserfs_generation' counter see FILESYSTEM_CHANGED() macro in reiserfs_fs.h */ #ifdef DISPLACE_NEW_PACKING_LOCALITIES - struct key key; /* key pointer, to pass to block allocator or + struct reiserfs_key key; /* key pointer, to pass to block allocator or another low-level subsystem */ #endif } ; @@ -1543,7 +1543,7 @@ struct buffer_info { struct item_operations { int (*bytes_number) (struct item_head * ih, int block_size); void (*decrement_key) (struct cpu_key *); - int (*is_left_mergeable) (struct key * ih, unsigned long bsize); + int (*is_left_mergeable) (struct reiserfs_key * ih, unsigned long bsize); void (*print_item) (struct item_head *, char * item); void (*check_item) (struct item_head *, char * item); @@ -1594,7 +1594,7 @@ extern struct item_operations * item_ops [TYPE_ANY + 1]; #define B_N_PITEM_HEAD(bh,item_num) ( (struct item_head * )((bh)->b_data + BLKH_SIZE) + (item_num) ) /* get key */ -#define B_N_PDELIM_KEY(bh,item_num) ( (struct key * )((bh)->b_data + BLKH_SIZE) + (item_num) ) +#define B_N_PDELIM_KEY(bh,item_num) ( (struct reiserfs_key * )((bh)->b_data + BLKH_SIZE) + (item_num) ) /* get the key */ #define B_N_PKEY(bh,item_num) ( &(B_N_PITEM_HEAD(bh,item_num)->ih_key) ) @@ -1832,11 +1832,11 @@ extern void copy_item_head(struct item_head * p_v_to, const struct item_head * p_v_from); // first key is in cpu form, second - le -extern int comp_keys (const struct key * le_key, +extern int comp_keys (const struct reiserfs_key * le_key, const struct cpu_key * cpu_key); -extern int comp_short_keys (const struct key * le_key, +extern int comp_short_keys (const struct reiserfs_key * le_key, const struct cpu_key * cpu_key); -extern void le_key2cpu_key (struct cpu_key * to, const struct key * from); +extern void le_key2cpu_key (struct cpu_key * to, const struct reiserfs_key * from); // both are cpu keys extern int comp_cpu_keys (const struct cpu_key *, const struct cpu_key *); @@ -1845,13 +1845,13 @@ extern int comp_short_cpu_keys (const struct cpu_key *, extern void cpu_key2cpu_key (struct cpu_key *, const struct cpu_key *); // both are in le form -extern int comp_le_keys (const struct key *, const struct key *); -extern int comp_short_le_keys (const struct key *, const struct key *); +extern int comp_le_keys (const struct reiserfs_key *, const struct reiserfs_key *); +extern int comp_short_le_keys (const struct reiserfs_key *, const struct reiserfs_key *); // // get key version from on disk key - kludge // -static inline int le_key_version (const struct key * key) +static inline int le_key_version (const struct reiserfs_key * key) { int type; @@ -1864,14 +1864,14 @@ static inline int le_key_version (const struct key * key) } -static inline void copy_key (struct key *to, const struct key *from) +static inline void copy_key (struct reiserfs_key *to, const struct reiserfs_key *from) { memcpy (to, from, KEY_SIZE); } int comp_items (const struct item_head * stored_ih, const struct path * p_s_path); -const struct key * get_rkey (const struct path * p_s_chk_path, +const struct reiserfs_key * get_rkey (const struct path * p_s_chk_path, const struct super_block * p_s_sb); inline int bin_search (const void * p_v_key, const void * p_v_base, int p_n_num, int p_n_width, int * p_n_pos); @@ -1913,7 +1913,7 @@ int reiserfs_delete_item (struct reiserfs_transaction_handle *th, struct buffer_head * p_s_un_bh); void reiserfs_delete_solid_item (struct reiserfs_transaction_handle *th, - struct inode *inode, struct key * key); + struct inode *inode, struct reiserfs_key * key); int reiserfs_delete_object (struct reiserfs_transaction_handle *th, struct inode * p_s_inode); int reiserfs_do_truncate (struct reiserfs_transaction_handle *th, struct inode * p_s_inode, struct page *, @@ -2131,7 +2131,7 @@ struct buffer_head * get_FEB (struct tree_balance *); struct __reiserfs_blocknr_hint { struct inode * inode; /* inode passed to allocator, if we allocate unf. nodes */ long block; /* file offset, in blocks */ - struct key key; + struct reiserfs_key key; struct path * path; /* search path, used by allocator to deternine search_start by * various ways */ struct reiserfs_transaction_handle * th; /* transaction handle is needed to log super blocks and -- cgit v1.2.3 From c6ac5ab1961150210b35d8fcef0248ae205f3645 Mon Sep 17 00:00:00 2001 From: David Howells Date: Mon, 18 Oct 2004 17:58:25 -0700 Subject: [PATCH] Add some key management specific error codes Here's a patch to add some new error codes specific to key management. Signed-Off-By: David Howells Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- include/asm-generic/errno.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/asm-generic/errno.h b/include/asm-generic/errno.h index d20dc0fed21d..5a72bb61a0a0 100644 --- a/include/asm-generic/errno.h +++ b/include/asm-generic/errno.h @@ -96,5 +96,9 @@ #define ENOMEDIUM 123 /* No medium found */ #define EMEDIUMTYPE 124 /* Wrong medium type */ +#define ENOKEY 125 /* Required key not available */ +#define EKEYEXPIRED 126 /* Key has expired */ +#define EKEYREVOKED 127 /* Key has been revoked */ +#define EKEYREJECTED 128 /* Key was rejected by service */ #endif -- cgit v1.2.3 From 322f317d1f560b90d8fe79d77fb248c98af92305 Mon Sep 17 00:00:00 2001 From: David Howells Date: Mon, 18 Oct 2004 17:58:38 -0700 Subject: [PATCH] keys: new error codes for Alpha, MIPS, PA-RISC, Sparc & Sparc64 The attached patch adds the new error codes I added for key-related errors to those archs that don't make use of , including Alpha, MIPS, PA-RISC, Sparc and Sparc64. This is required to compile with CONFIG_KEYS on those platforms. Signed-Off-By: David Howells Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- include/asm-alpha/errno.h | 4 ++++ include/asm-mips/errno.h | 4 ++++ include/asm-parisc/errno.h | 4 ++++ include/asm-sparc/errno.h | 4 ++++ include/asm-sparc64/errno.h | 4 ++++ 5 files changed, 20 insertions(+) diff --git a/include/asm-alpha/errno.h b/include/asm-alpha/errno.h index 677dc9b2989e..a521d0100f99 100644 --- a/include/asm-alpha/errno.h +++ b/include/asm-alpha/errno.h @@ -110,5 +110,9 @@ #define ENOMEDIUM 129 /* No medium found */ #define EMEDIUMTYPE 130 /* Wrong medium type */ +#define ENOKEY 131 /* Required key not available */ +#define EKEYEXPIRED 132 /* Key has expired */ +#define EKEYREVOKED 133 /* Key has been revoked */ +#define EKEYREJECTED 134 /* Key was rejected by service */ #endif diff --git a/include/asm-mips/errno.h b/include/asm-mips/errno.h index 35d47a882801..2b458f9538cd 100644 --- a/include/asm-mips/errno.h +++ b/include/asm-mips/errno.h @@ -110,6 +110,10 @@ */ #define ENOMEDIUM 159 /* No medium found */ #define EMEDIUMTYPE 160 /* Wrong medium type */ +#define ENOKEY 161 /* Required key not available */ +#define EKEYEXPIRED 162 /* Key has expired */ +#define EKEYREVOKED 163 /* Key has been revoked */ +#define EKEYREJECTED 164 /* Key was rejected by service */ #define EDQUOT 1133 /* Quota exceeded */ diff --git a/include/asm-parisc/errno.h b/include/asm-parisc/errno.h index 3b0fbce3e0b0..a10f109770f1 100644 --- a/include/asm-parisc/errno.h +++ b/include/asm-parisc/errno.h @@ -67,6 +67,10 @@ #define EREMOTEIO 181 /* Remote I/O error */ #define ENOMEDIUM 182 /* No medium found */ #define EMEDIUMTYPE 183 /* Wrong medium type */ +#define ENOKEY 184 /* Required key not available */ +#define EKEYEXPIRED 185 /* Key has expired */ +#define EKEYREVOKED 186 /* Key has been revoked */ +#define EKEYREJECTED 187 /* Key was rejected by service */ /* We now return you to your regularly scheduled HPUX. */ diff --git a/include/asm-sparc/errno.h b/include/asm-sparc/errno.h index 5b0194e6f78f..ee91f3b44444 100644 --- a/include/asm-sparc/errno.h +++ b/include/asm-sparc/errno.h @@ -101,5 +101,9 @@ #define ENOMEDIUM 125 /* No medium found */ #define EMEDIUMTYPE 126 /* Wrong medium type */ +#define ENOKEY 127 /* Required key not available */ +#define EKEYEXPIRED 128 /* Key has expired */ +#define EKEYREVOKED 129 /* Key has been revoked */ +#define EKEYREJECTED 130 /* Key was rejected by service */ #endif diff --git a/include/asm-sparc64/errno.h b/include/asm-sparc64/errno.h index e7890f0e9aa4..6dc57bc985c0 100644 --- a/include/asm-sparc64/errno.h +++ b/include/asm-sparc64/errno.h @@ -101,5 +101,9 @@ #define ENOMEDIUM 125 /* No medium found */ #define EMEDIUMTYPE 126 /* Wrong medium type */ +#define ENOKEY 127 /* Required key not available */ +#define EKEYEXPIRED 128 /* Key has expired */ +#define EKEYREVOKED 129 /* Key has been revoked */ +#define EKEYREJECTED 130 /* Key was rejected by service */ #endif /* !(_SPARC64_ERRNO_H) */ -- cgit v1.2.3 From e4262f594a9e36cf93a8789fc7e02e9ff0d1f564 Mon Sep 17 00:00:00 2001 From: David Howells Date: Mon, 18 Oct 2004 17:58:51 -0700 Subject: [PATCH] implement in-kernel keys & keyring management The feature set the patch includes: - Key attributes: - Key type - Description (by which a key of a particular type can be selected) - Payload - UID, GID and permissions mask - Expiry time - Keyrings (just a type of key that holds links to other keys) - User-defined keys - Key revokation - Access controls - Per user key-count and key-memory consumption quota - Three std keyrings per task: per-thread, per-process, session - Two std keyrings per user: per-user and default-user-session - prctl() functions for key and keyring creation and management - Kernel interfaces for filesystem, blockdev, net stack access - JIT key creation by usermode helper There are also two utility programs available: (*) http://people.redhat.com/~dhowells/keys/keyctl.c A comprehensive key management tool, permitting all the interfaces available to userspace to be exercised. (*) http://people.redhat.com/~dhowells/keys/request-key An example shell script (to be installed in /sbin) for instantiating a key. Signed-Off-By: David Howells Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- Documentation/keys.txt | 836 +++++++++++++++++++++++++++++++++ arch/i386/kernel/entry.S | 5 +- fs/afs/main.c | 6 +- fs/exec.c | 10 +- include/asm-i386/unistd.h | 13 +- include/linux/key-ui.h | 97 ++++ include/linux/key.h | 284 ++++++++++++ include/linux/keyctl.h | 39 ++ include/linux/prctl.h | 1 - include/linux/sched.h | 16 +- include/linux/syscalls.h | 15 + kernel/exit.c | 2 + kernel/fork.c | 13 +- kernel/sys.c | 20 +- kernel/user.c | 16 + security/Kconfig | 29 ++ security/Makefile | 1 + security/keys/Makefile | 13 + security/keys/internal.h | 109 +++++ security/keys/key.c | 1039 ++++++++++++++++++++++++++++++++++++++++++ security/keys/keyctl.c | 991 ++++++++++++++++++++++++++++++++++++++++ security/keys/keyring.c | 895 ++++++++++++++++++++++++++++++++++++ security/keys/proc.c | 251 ++++++++++ security/keys/process_keys.c | 640 ++++++++++++++++++++++++++ security/keys/request_key.c | 337 ++++++++++++++ security/keys/user_defined.c | 191 ++++++++ 26 files changed, 5852 insertions(+), 17 deletions(-) create mode 100644 Documentation/keys.txt create mode 100644 include/linux/key-ui.h create mode 100644 include/linux/key.h create mode 100644 include/linux/keyctl.h create mode 100644 security/keys/Makefile create mode 100644 security/keys/internal.h create mode 100644 security/keys/key.c create mode 100644 security/keys/keyctl.c create mode 100644 security/keys/keyring.c create mode 100644 security/keys/proc.c create mode 100644 security/keys/process_keys.c create mode 100644 security/keys/request_key.c create mode 100644 security/keys/user_defined.c diff --git a/Documentation/keys.txt b/Documentation/keys.txt new file mode 100644 index 000000000000..36cbb0dd0f32 --- /dev/null +++ b/Documentation/keys.txt @@ -0,0 +1,836 @@ + ============================ + KERNEL KEY RETENTION SERVICE + ============================ + +This service allows cryptographic keys, authentication tokens, cross-domain +user mappings, and similar to be cached in the kernel for the use of +filesystems other kernel services. + +Keyrings are permitted; these are a special type of key that can hold links to +other keys. Processes each have three standard keyring subscriptions that a +kernel service can search for relevant keys. + +The key service can be configured on by enabling: + + "Security options"/"Enable access key retention support" (CONFIG_KEYS) + +This document has the following sections: + + - Key overview + - Key service overview + - Key access permissions + - New procfs files + - Userspace system call interface + - Kernel services + - Defining a key type + - Request-key callback service + - Key access filesystem + + +============ +KEY OVERVIEW +============ + +In this context, keys represent units of cryptographic data, authentication +tokens, keyrings, etc.. These are represented in the kernel by struct key. + +Each key has a number of attributes: + + - A serial number. + - A type. + - A description (for matching a key in a search). + - Access control information. + - An expiry time. + - A payload. + - State. + + + (*) Each key is issued a serial number of type key_serial_t that is unique + for the lifetime of that key. All serial numbers are positive non-zero + 32-bit integers. + + Userspace programs can use a key's serial numbers as a way to gain access + to it, subject to permission checking. + + (*) Each key is of a defined "type". Types must be registered inside the + kernel by a kernel service (such as a filesystem) before keys of that + type can be added or used. Userspace programs cannot define new types + directly. + + Key types are represented in the kernel by struct key_type. This defines + a number of operations that can be performed on a key of that type. + + Should a type be removed from the system, all the keys of that type will + be invalidated. + + (*) Each key has a description. This should be a printable string. The key + type provides an operation to perform a match between the description on + a key and a criterion string. + + (*) Each key has an owner user ID, a group ID and a permissions mask. These + are used to control what a process may do to a key from userspace, and + whether a kernel service will be able to find the key. + + (*) Each key can be set to expire at a specific time by the key type's + instantiation function. Keys can also be immortal. + + (*) Each key can have a payload. This is a quantity of data that represent + the actual "key". In the case of a keyring, this is a list of keys to + which the keyring links; in the case of a user-defined key, it's an + arbitrary blob of data. + + Having a payload is not required; and the payload can, in fact, just be a + value stored in the struct key itself. + + When a key is instantiated, the key type's instantiation function is + called with a blob of data, and that then creates the key's payload in + some way. + + Similarly, when userspace wants to read back the contents of the key, if + permitted, another key type operation will be called to convert the key's + attached payload back into a blob of data. + + (*) Each key can be in one of a number of basic states: + + (*) Uninstantiated. The key exists, but does not have any data + attached. Keys being requested from userspace will be in this state. + + (*) Instantiated. This is the normal state. The key is fully formed, and + has data attached. + + (*) Negative. This is a relatively short-lived state. The key acts as a + note saying that a previous call out to userspace failed, and acts as + a throttle on key lookups. A negative key can be updated to a normal + state. + + (*) Expired. Keys can have lifetimes set. If their lifetime is exceeded, + they traverse to this state. An expired key can be updated back to a + normal state. + + (*) Revoked. A key is put in this state by userspace action. It can't be + found or operated upon (apart from by unlinking it). + + (*) Dead. The key's type was unregistered, and so the key is now useless. + + +==================== +KEY SERVICE OVERVIEW +==================== + +The key service provides a number of features besides keys: + + (*) The key service defines two special key types: + + (+) "keyring" + + Keyrings are special keys that contain a list of other keys. Keyring + lists can be modified using various system calls. Keyrings should not + be given a payload when created. + + (+) "user" + + A key of this type has a description and a payload that are arbitrary + blobs of data. These can be created, updated and read by userspace, + and aren't intended for use by kernel services. + + (*) Each process subscribes to three keyrings: a thread-specific keyring, a + process-specific keyring, and a session-specific keyring. + + The thread-specific keyring is discarded from the child when any sort of + clone, fork, vfork or execve occurs. A new keyring is created only when + required. + + The process-specific keyring is replaced with an empty one in the child + on clone, fork, vfork unless CLONE_THREAD is supplied, in which case it + is shared. execve also discards the process's process keyring and creates + a new one. + + The session-specific keyring is persistent across clone, fork, vfork and + execve, even when the latter executes a set-UID or set-GID binary. A + process can, however, replace its current session keyring with a new one + by using PR_JOIN_SESSION_KEYRING. It is permitted to request an anonymous + new one, or to attempt to create or join one of a specific name. + + The ownership of the thread and process-specific keyrings changes when + the real UID and GID of the thread changes. + + (*) Each user ID resident in the system holds two special keyrings: a user + specific keyring and a default user session keyring. The default session + keyring is initialised with a link to the user-specific keyring. + + When a process changes its real UID, if it used to have no session key, it + will be subscribed to the default session key for the new UID. + + If a process attempts to access its session key when it doesn't have one, + it will be subscribed to the default for its current UID. + + (*) Each user has two quotas against which the keys they own are tracked. One + limits the total number of keys and keyrings, the other limits the total + amount of description and payload space that can be consumed. + + The user can view information on this and other statistics through procfs + files. + + Process-specific and thread-specific keyrings are not counted towards a + user's quota. + + If a system call that modifies a key or keyring in some way would put the + user over quota, the operation is refused and error EDQUOT is returned. + + (*) There's a system call interface by which userspace programs can create + and manipulate keys and keyrings. + + (*) There's a kernel interface by which services can register types and + search for keys. + + (*) There's a way for the a search done from the kernel to call back to + userspace to request a key that can't be found in a process's keyrings. + + (*) An optional filesystem is available through which the key database can be + viewed and manipulated. + + +====================== +KEY ACCESS PERMISSIONS +====================== + +Keys have an owner user ID, a group access ID, and a permissions mask. The +mask has up to eight bits each for user, group and other access. Only five of +each set of eight bits are defined. These permissions granted are: + + (*) View + + This permits a key or keyring's attributes to be viewed - including key + type and description. + + (*) Read + + This permits a key's payload to be viewed or a keyring's list of linked + keys. + + (*) Write + + This permits a key's payload to be instantiated or updated, or it allows + a link to be added to or removed from a keyring. + + (*) Search + + This permits keyrings to be searched and keys to be found. Searches can + only recurse into nested keyrings that have search permission set. + + (*) Link + + This permits a key or keyring to be linked to. To create a link from a + keyring to a key, a process must have Write permission on the keyring and + Link permission on the key. + +For changing the ownership, group ID or permissions mask, being the owner of +the key or having the sysadmin capability is sufficient. + + +================ +NEW PROCFS FILES +================ + +Two files have been added to procfs by which an administrator can find out +about the status of the key service: + + (*) /proc/keys + + This lists all the keys on the system, giving information about their + type, description and permissions. The payload of the key is not + available this way: + + SERIAL FLAGS USAGE EXPY PERM UID GID TYPE DESCRIPTION: SUMMARY + 00000001 I----- 39 perm 1f0000 0 0 keyring _uid_ses.0: 1/4 + 00000002 I----- 2 perm 1f0000 0 0 keyring _uid.0: empty + 00000007 I----- 1 perm 1f0000 0 0 keyring _pid.1: empty + 0000018d I----- 1 perm 1f0000 0 0 keyring _pid.412: empty + 000004d2 I--Q-- 1 perm 1f0000 32 -1 keyring _uid.32: 1/4 + 000004d3 I--Q-- 3 perm 1f0000 32 -1 keyring _uid_ses.32: empty + 00000892 I--QU- 1 perm 1f0000 0 0 user metal:copper: 0 + 00000893 I--Q-N 1 35s 1f0000 0 0 user metal:silver: 0 + 00000894 I--Q-- 1 10h 1f0000 0 0 user metal:gold: 0 + + The flags are: + + I Instantiated + R Revoked + D Dead + Q Contributes to user's quota + U Under contruction by callback to userspace + N Negative key + + This file must be enabled at kernel configuration time as it allows anyone + to list the keys database. + + (*) /proc/key-users + + This file lists the tracking data for each user that has at least one key + on the system. Such data includes quota information and statistics: + + [root@andromeda root]# cat /proc/key-users + 0: 46 45/45 1/100 13/10000 + 29: 2 2/2 2/100 40/10000 + 32: 2 2/2 2/100 40/10000 + 38: 2 2/2 2/100 40/10000 + + The format of each line is + : User ID to which this applies + Structure refcount + / Total number of keys and number instantiated + / Key count quota + / Key size quota + + +=============================== +USERSPACE SYSTEM CALL INTERFACE +=============================== + +Userspace can manipulate keys directly through three new syscalls: add_key, +request_key and keyctl. The latter provides a number of functions for +manipulating keys. + +When referring to a key directly, userspace programs should use the key's +serial number (a positive 32-bit integer). However, there are some special +values available for referring to special keys and keyrings that relate to the +process making the call: + + CONSTANT VALUE KEY REFERENCED + ============================== ====== =========================== + KEY_SPEC_THREAD_KEYRING -1 thread-specific keyring + KEY_SPEC_PROCESS_KEYRING -2 process-specific keyring + KEY_SPEC_SESSION_KEYRING -3 session-specific keyring + KEY_SPEC_USER_KEYRING -4 UID-specific keyring + KEY_SPEC_USER_SESSION_KEYRING -5 UID-session keyring + KEY_SPEC_GROUP_KEYRING -6 GID-specific keyring + + +The main syscalls are: + + (*) Create a new key of given type, description and payload and add it to the + nominated keyring: + + key_serial_t add_key(const char *type, const char *desc, + const void *payload, size_t plen, + key_serial_t keyring); + + If a key of the same type and description as that proposed already exists + in the keyring, this will try to update it with the given payload, or it + will return error EEXIST if that function is not supported by the key + type. The process must also have permission to write to the key to be + able to update it. The new key will have all user permissions granted and + no group or third party permissions. + + Otherwise, this will attempt to create a new key of the specified type + and description, and to instantiate it with the supplied payload and + attach it to the keyring. In this case, an error will be generated if the + process does not have permission to write to the keyring. + + The payload is optional, and the pointer can be NULL if not required by + the type. The payload is plen in size, and plen can be zero for an empty + payload. + + A new keyring can be generated by setting type "keyring", the keyring + name as the description (or NULL) and setting the payload to NULL. + + User defined keys can be created by specifying type "user". It is + recommended that a user defined key's description by prefixed with a type + ID and a colon, such as "krb5tgt:" for a Kerberos 5 ticket granting + ticket. + + Any other type must have been registered with the kernel in advance by a + kernel service such as a filesystem. + + The ID of the new or updated key is returned if successful. + + + (*) Search the process's keyrings for a key, potentially calling out to + userspace to create it. + + key_serial_t request_key(const char *type, const char *description, + const char *callout_info, + key_serial_t dest_keyring); + + This function searches all the process's keyrings in the order thread, + process, session for a matching key. This works very much like + KEYCTL_SEARCH, including the optional attachment of the discovered key to + a keyring. + + If a key cannot be found, and if callout_info is not NULL, then + /sbin/request-key will be invoked in an attempt to obtain a key. The + callout_info string will be passed as an argument to the program. + + +The keyctl syscall functions are: + + (*) Map a special key ID to a real key ID for this process: + + key_serial_t keyctl(KEYCTL_GET_KEYRING_ID, key_serial_t id, + int create); + + The special key specified by "id" is looked up (with the key being + created if necessary) and the ID of the key or keyring thus found is + returned if it exists. + + If the key does not yet exist, the key will be created if "create" is + non-zero; and the error ENOKEY will be returned if "create" is zero. + + + (*) Replace the session keyring this process subscribes to with a new one: + + key_serial_t keyctl(KEYCTL_JOIN_SESSION_KEYRING, const char *name); + + If name is NULL, an anonymous keyring is created attached to the process + as its session keyring, displacing the old session keyring. + + If name is not NULL, if a keyring of that name exists, the process + attempts to attach it as the session keyring, returning an error if that + is not permitted; otherwise a new keyring of that name is created and + attached as the session keyring. + + To attach to a named keyring, the keyring must have search permission for + the process's ownership. + + The ID of the new session keyring is returned if successful. + + + (*) Update the specified key: + + long keyctl(KEYCTL_UPDATE, key_serial_t key, const void *payload, + size_t plen); + + This will try to update the specified key with the given payload, or it + will return error EOPNOTSUPP if that function is not supported by the key + type. The process must also have permission to write to the key to be + able to update it. + + The payload is of length plen, and may be absent or empty as for + add_key(). + + + (*) Revoke a key: + + long keyctl(KEYCTL_REVOKE, key_serial_t key); + + This makes a key unavailable for further operations. Further attempts to + use the key will be met with error EKEYREVOKED, and the key will no longer + be findable. + + + (*) Change the ownership of a key: + + long keyctl(KEYCTL_CHOWN, key_serial_t key, uid_t uid, gid_t gid); + + This function permits a key's owner and group ID to be changed. Either + one of uid or gid can be set to -1 to suppress that change. + + Only the superuser can change a key's owner to something other than the + key's current owner. Similarly, only the superuser can change a key's + group ID to something other than the calling process's group ID or one of + its group list members. + + + (*) Change the permissions mask on a key: + + long keyctl(KEYCTL_SETPERM, key_serial_t key, key_perm_t perm); + + This function permits the owner of a key or the superuser to change the + permissions mask on a key. + + Only bits the available bits are permitted; if any other bits are set, + error EINVAL will be returned. + + + (*) Describe a key: + + long keyctl(KEYCTL_DESCRIBE, key_serial_t key, char *buffer, + size_t buflen); + + This function returns a summary of the key's attributes (but not its + payload data) as a string in the buffer provided. + + Unless there's an error, it always returns the amount of data it could + produce, even if that's too big for the buffer, but it won't copy more + than requested to userspace. If the buffer pointer is NULL then no copy + will take place. + + A process must have view permission on the key for this function to be + successful. + + If successful, a string is placed in the buffer in the following format: + + ;;;; + + Where type and description are strings, uid and gid are decimal, and perm + is hexadecimal. A NUL character is included at the end of the string if + the buffer is sufficiently big. + + This can be parsed with + + sscanf(buffer, "%[^;];%d;%d;%o;%s", type, &uid, &gid, &mode, desc); + + + (*) Clear out a keyring: + + long keyctl(KEYCTL_CLEAR, key_serial_t keyring); + + This function clears the list of keys attached to a keyring. The calling + process must have write permission on the keyring, and it must be a + keyring (or else error ENOTDIR will result). + + + (*) Link a key into a keyring: + + long keyctl(KEYCTL_LINK, key_serial_t keyring, key_serial_t key); + + This function creates a link from the keyring to the key. The process + must have write permission on the keyring and must have link permission + on the key. + + Should the keyring not be a keyring, error ENOTDIR will result; and if + the keyring is full, error ENFILE will result. + + The link procedure checks the nesting of the keyrings, returning ELOOP if + it appears to deep or EDEADLK if the link would introduce a cycle. + + + (*) Unlink a key or keyring from another keyring: + + long keyctl(KEYCTL_UNLINK, key_serial_t keyring, key_serial_t key); + + This function looks through the keyring for the first link to the + specified key, and removes it if found. Subsequent links to that key are + ignored. The process must have write permission on the keyring. + + If the keyring is not a keyring, error ENOTDIR will result; and if the + key is not present, error ENOENT will be the result. + + + (*) Search a keyring tree for a key: + + key_serial_t keyctl(KEYCTL_SEARCH, key_serial_t keyring, + const char *type, const char *description, + key_serial_t dest_keyring); + + This searches the keyring tree headed by the specified keyring until a + key is found that matches the type and description criteria. Each keyring + is checked for keys before recursion into its children occurs. + + The process must have search permission on the top level keyring, or else + error EACCES will result. Only keyrings that the process has search + permission on will be recursed into, and only keys and keyrings for which + a process has search permission can be matched. If the specified keyring + is not a keyring, ENOTDIR will result. + + If the search succeeds, the function will attempt to link the found key + into the destination keyring if one is supplied (non-zero ID). All the + constraints applicable to KEYCTL_LINK apply in this case too. + + Error ENOKEY, EKEYREVOKED or EKEYEXPIRED will be returned if the search + fails. On success, the resulting key ID will be returned. + + + (*) Read the payload data from a key: + + key_serial_t keyctl(KEYCTL_READ, key_serial_t keyring, char *buffer, + size_t buflen); + + This function attempts to read the payload data from the specified key + into the buffer. The process must have read permission on the key to + succeed. + + The returned data will be processed for presentation by the key type. For + instance, a keyring will return an array of key_serial_t entries + representing the IDs of all the keys to which it is subscribed. The user + defined key type will return its data as is. If a key type does not + implement this function, error EOPNOTSUPP will result. + + As much of the data as can be fitted into the buffer will be copied to + userspace if the buffer pointer is not NULL. + + On a successful return, the function will always return the amount of + data available rather than the amount copied. + + + (*) Instantiate a partially constructed key. + + key_serial_t keyctl(KEYCTL_INSTANTIATE, key_serial_t key, + const void *payload, size_t plen, + key_serial_t keyring); + + If the kernel calls back to userspace to complete the instantiation of a + key, userspace should use this call to supply data for the key before the + invoked process returns, or else the key will be marked negative + automatically. + + The process must have write access on the key to be able to instantiate + it, and the key must be uninstantiated. + + If a keyring is specified (non-zero), the key will also be linked into + that keyring, however all the constraints applying in KEYCTL_LINK apply + in this case too. + + The payload and plen arguments describe the payload data as for add_key(). + + + (*) Negatively instantiate a partially constructed key. + + key_serial_t keyctl(KEYCTL_NEGATE, key_serial_t key, + unsigned timeout, key_serial_t keyring); + + If the kernel calls back to userspace to complete the instantiation of a + key, userspace should use this call mark the key as negative before the + invoked process returns if it is unable to fulfil the request. + + The process must have write access on the key to be able to instantiate + it, and the key must be uninstantiated. + + If a keyring is specified (non-zero), the key will also be linked into + that keyring, however all the constraints applying in KEYCTL_LINK apply + in this case too. + + +=============== +KERNEL SERVICES +=============== + +The kernel services for key managment are fairly simple to deal with. They can +be broken down into two areas: keys and key types. + +Dealing with keys is fairly straightforward. Firstly, the kernel service +registers its type, then it searches for a key of that type. It should retain +the key as long as it has need of it, and then it should release it. For a +filesystem or device file, a search would probably be performed during the +open call, and the key released upon close. How to deal with conflicting keys +due to two different users opening the same file is left to the filesystem +author to solve. + +When accessing a key's payload data, the key->lock should be at least read +locked, or else the data may be changed by update during the access. + +(*) To search for a key, call: + + struct key *request_key(const struct key_type *type, + const char *description, + const char *callout_string); + + This is used to request a key or keyring with a description that matches + the description specified according to the key type's match function. This + permits approximate matching to occur. If callout_string is not NULL, then + /sbin/request-key will be invoked in an attempt to obtain the key from + userspace. In that case, callout_string will be passed as an argument to + the program. + + Should the function fail error ENOKEY, EKEYEXPIRED or EKEYREVOKED will be + returned. + + +(*) When it is no longer required, the key should be released using: + + void key_put(struct key *key); + + This can be called from interrupt context. If CONFIG_KEYS is not set then + the argument will not be parsed. + + +(*) Extra references can be made to a key by calling the following function: + + struct key *key_get(struct key *key); + + These need to be disposed of by calling key_put() when they've been + finished with. The key pointer passed in will be returned. If the pointer + is NULL or CONFIG_KEYS is not set then the key will not be dereferenced and + no increment will take place. + + +(*) A key's serial number can be obtained by calling: + + key_serial_t key_serial(struct key *key); + + If key is NULL or if CONFIG_KEYS is not set then 0 will be returned (in the + latter case without parsing the argument). + + +(*) If a keyring was found in the search, this can be further searched by: + + struct key *keyring_search(struct key *keyring, + const struct key_type *type, + const char *description) + + This searches the keyring tree specified for a matching key. Error ENOKEY + is returned upon failure. If successful, the returned key will need to be + released. + + +(*) To check the validity of a key, this function can be called: + + int validate_key(struct key *key); + + This checks that the key in question hasn't expired or and hasn't been + revoked. Should the key be invalid, error EKEYEXPIRED or EKEYREVOKED will + be returned. If the key is NULL or if CONFIG_KEYS is not set then 0 will be + returned (in the latter case without parsing the argument). + + +(*) To register a key type, the following function should be called: + + int register_key_type(struct key_type *type); + + This will return error EEXIST if a type of the same name is already + present. + + +(*) To unregister a key type, call: + + void unregister_key_type(struct key_type *type); + + +=================== +DEFINING A KEY TYPE +=================== + +A kernel service may want to define its own key type. For instance, an AFS +filesystem might want to define a Kerberos 5 ticket key type. To do this, it +author fills in a struct key_type and registers it with the system. + +The structure has a number of fields, some of which are mandatory: + + (*) const char *name + + The name of the key type. This is used to translate a key type name + supplied by userspace into a pointer to the structure. + + + (*) size_t def_datalen + + This is optional - it supplies the default payload data length as + contributed to the quota. If the key type's payload is always or almost + always the same size, then this is a more efficient way to do things. + + The data length (and quota) on a particular key can always be changed + during instantiation or update by calling: + + int key_payload_reserve(struct key *key, size_t datalen); + + With the revised data length. Error EDQUOT will be returned if this is + not viable. + + + (*) int (*instantiate)(struct key *key, const void *data, size_t datalen); + + This method is called to attach a payload to a key during + construction. The payload attached need not bear any relation to the data + passed to this function. + + If the amount of data attached to the key differs from the size in + keytype->def_datalen, then key_payload_reserve() should be called. + + + (*) int (*duplicate)(struct key *key, const struct key *source); + + If this type of key can be duplicated, then this method should be + provided. It is called to copy the payload attached to the source into + the new key. The data length on the new key will have been updated and + the quota adjusted already. + + The source key will be locked against change on the source->sem, so it is + safe to sleep here. + + + (*) int (*update)(struct key *key, const void *data, size_t datalen); + + If this type of key can be updated, then this method should be + provided. It is called to update a key's payload from the blob of data + provided. + + key_payload_reserve() should be called if the data length might change + before any changes are actually made. Note that if this succeeds, the + type is committed to changing the key because it's already been altered, + so all memory allocation must be done first. + + The key will be locked against other changers on key->sem, so it is safe + to sleep here. + + key_payload_reserve() should be called with the key->lock write locked, + and the changes to the key's attached payload should be made before the + key is locked. + + + (*) int (*match)(const struct key *key, const void *desc); + + This method is called to match a key against a description. It should + return non-zero if the two match, zero if they don't. + + + (*) void (*destroy)(struct key *key); + + This method is optional. It is called to discard the payload data on a + key when it is being destroyed. + + + (*) void (*describe)(const struct key *key, struct seq_file *p); + + This method is optional. It is called during /proc/keys reading to + summarise a key in text form. + + + (*) long (*read)(const struct key *key, char __user *buffer, size_t buflen); + + This method is optional. It is called by KEYCTL_READ to translate the + key's payload into something a blob of data for userspace to deal + with. Ideally, the blob should be in the same format as that passed in to + the instantiate and update methods. + + If successful, the blob size that could be produced should be returned + rather than the size copied. + + +============================ +REQUEST-KEY CALLBACK SERVICE +============================ + +To create a new key, the kernel will attempt to execute the following command +line: + + /sbin/request-key create \ + + + is the key being constructed, and the three keyrings are the process +keyrings from the process that caused the search to be issued. These are +included for two reasons: + + (1) There may be an authentication token in one of the keyrings that is + required to obtain the key, eg: a Kerberos Ticket-Granting Ticket. + + (2) The new key should probably be cached in one of these rings. + +This program should set it UID and GID to those specified before attempting to +access any more keys. It may then look around for a user specific process to +hand the request off to (perhaps a path held in placed in another key by, for +example, the KDE desktop manager). + +The program (or whatever it calls) should finish construction of the key by +calling KEYCTL_INSTANTIATE, which also permits it to cache the key in one of +the keyrings (probably the session ring) before returning. Alternatively, the +key can be marked as negative with KEYCTL_NEGATE; this also permits the key to +be cached in one of the keyrings. + +If it returns with the key remaining in the unconstructed state, the key will +be marked as being negative, it will be added to the session keyring, and an +error will be returned to the key requestor. + +Supplementary information may be provided from whoever or whatever invoked +this service. This will be passed as the parameter. If no such +information was made available, then "-" will be passed as this parameter +instead. + + +Similarly, the kernel may attempt to update an expired or a soon to expire key +by executing: + + /sbin/request-key update \ + + +In this case, the program isn't required to actually attach the key to a ring; +the rings are provided for reference. diff --git a/arch/i386/kernel/entry.S b/arch/i386/kernel/entry.S index b158ceb8b126..c6a2e244a073 100644 --- a/arch/i386/kernel/entry.S +++ b/arch/i386/kernel/entry.S @@ -867,6 +867,9 @@ ENTRY(sys_call_table) .long sys_mq_getsetattr .long sys_ni_syscall /* reserved for kexec */ .long sys_waitid - .long sys_setaltroot + .long sys_setaltroot /* 285 */ + .long sys_add_key + .long sys_request_key + .long sys_keyctl syscall_table_size=(.-sys_call_table) diff --git a/fs/afs/main.c b/fs/afs/main.c index 955dbef62b69..c8775699fb44 100644 --- a/fs/afs/main.c +++ b/fs/afs/main.c @@ -100,7 +100,7 @@ static int afs_init(void) goto error; #endif -#ifdef CONFIG_KEYS +#ifdef CONFIG_KEYS_TURNED_OFF ret = afs_key_register(); if (ret < 0) goto error_cache; @@ -142,7 +142,7 @@ static int afs_init(void) error_kafstimod: afs_kafstimod_stop(); error_keys: -#ifdef CONFIG_KEYS +#ifdef CONFIG_KEYS_TURNED_OFF afs_key_unregister(); error_cache: #endif @@ -169,7 +169,7 @@ static void __exit afs_exit(void) afs_kafstimod_stop(); afs_kafsasyncd_stop(); afs_cell_purge(); -#ifdef CONFIG_KEYS +#ifdef CONFIG_KEYS_TURNED_OFF afs_key_unregister(); #endif #ifdef AFS_CACHING_SUPPORT diff --git a/fs/exec.c b/fs/exec.c index e715541b2db4..52ffe4e3295d 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -848,8 +849,10 @@ int flush_old_exec(struct linux_binprm * bprm) if (bprm->e_uid != current->euid || bprm->e_gid != current->egid || permission(bprm->file->f_dentry->d_inode,MAY_READ, NULL) || - (bprm->interp_flags & BINPRM_FLAGS_ENFORCE_NONDUMP)) + (bprm->interp_flags & BINPRM_FLAGS_ENFORCE_NONDUMP)) { + suid_keys(current); current->mm->dumpable = 0; + } /* An exec changes our domain. We are no longer part of the thread group */ @@ -943,6 +946,11 @@ static inline int unsafe_exec(struct task_struct *p) void compute_creds(struct linux_binprm *bprm) { int unsafe; + + if (bprm->e_uid != current->uid) + suid_keys(current); + exec_keys(current); + task_lock(current); unsafe = unsafe_exec(current); security_bprm_apply_creds(bprm, unsafe); diff --git a/include/asm-i386/unistd.h b/include/asm-i386/unistd.h index 2d9e45d2d5f8..e094249c8942 100644 --- a/include/asm-i386/unistd.h +++ b/include/asm-i386/unistd.h @@ -291,14 +291,19 @@ #define __NR_sys_kexec_load 283 #define __NR_waitid 284 #define __NR_sys_setaltroot 285 +#define __NR_add_key 286 +#define __NR_request_key 287 +#define __NR_keyctl 288 -#define NR_syscalls 286 - -/* user-visible error numbers are in the range -1 - -124: see */ +#define NR_syscalls 289 +/* + * user-visible error numbers are in the range -1 - -128: see + * + */ #define __syscall_return(type, res) \ do { \ - if ((unsigned long)(res) >= (unsigned long)(-125)) { \ + if ((unsigned long)(res) >= (unsigned long)(-(128 + 1))) { \ errno = -(res); \ res = -1; \ } \ diff --git a/include/linux/key-ui.h b/include/linux/key-ui.h new file mode 100644 index 000000000000..60cc7b762e78 --- /dev/null +++ b/include/linux/key-ui.h @@ -0,0 +1,97 @@ +/* key-ui.h: key userspace interface stuff for use by keyfs + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#ifndef _LINUX_KEY_UI_H +#define _LINUX_KEY_UI_H + +#include + +/* the key tree */ +extern struct rb_root key_serial_tree; +extern spinlock_t key_serial_lock; + +/* required permissions */ +#define KEY_VIEW 0x01 /* require permission to view attributes */ +#define KEY_READ 0x02 /* require permission to read content */ +#define KEY_WRITE 0x04 /* require permission to update / modify */ +#define KEY_SEARCH 0x08 /* require permission to search (keyring) or find (key) */ +#define KEY_LINK 0x10 /* require permission to link */ +#define KEY_ALL 0x1f /* all the above permissions */ + +/* + * the keyring payload contains a list of the keys to which the keyring is + * subscribed + */ +struct keyring_list { + unsigned maxkeys; /* max keys this list can hold */ + unsigned nkeys; /* number of keys currently held */ + struct key *keys[0]; +}; + + +/* + * check to see whether permission is granted to use a key in the desired way + */ +static inline int key_permission(const struct key *key, key_perm_t perm) +{ + key_perm_t kperm; + + if (key->uid == current->fsuid) + kperm = key->perm >> 16; + else if (key->gid != -1 && + key->perm & KEY_GRP_ALL && + in_group_p(key->gid) + ) + kperm = key->perm >> 8; + else + kperm = key->perm; + + kperm = kperm & perm & KEY_ALL; + + return kperm == perm; +} + +/* + * check to see whether permission is granted to use a key in at least one of + * the desired ways + */ +static inline int key_any_permission(const struct key *key, key_perm_t perm) +{ + key_perm_t kperm; + + if (key->uid == current->fsuid) + kperm = key->perm >> 16; + else if (key->gid != -1 && + key->perm & KEY_GRP_ALL && + in_group_p(key->gid) + ) + kperm = key->perm >> 8; + else + kperm = key->perm; + + kperm = kperm & perm & KEY_ALL; + + return kperm != 0; +} + + +extern struct key *lookup_user_key(key_serial_t id, int create, int part, + key_perm_t perm); + +extern long join_session_keyring(const char *name); + +extern struct key_type *key_type_lookup(const char *type); +extern void key_type_put(struct key_type *ktype); + +#define key_negative_timeout 60 /* default timeout on a negative key's existence */ + + +#endif /* _LINUX_KEY_UI_H */ diff --git a/include/linux/key.h b/include/linux/key.h new file mode 100644 index 000000000000..e914be777c4a --- /dev/null +++ b/include/linux/key.h @@ -0,0 +1,284 @@ +/* key.h: authentication token and access key management + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * + * See Documentation/keys.txt for information on keys/keyrings. + */ + +#ifndef _LINUX_KEY_H +#define _LINUX_KEY_H + +#include +#include +#include +#include +#include + +#ifdef __KERNEL__ + +/* key handle serial number */ +typedef int32_t key_serial_t; + +/* key handle permissions mask */ +typedef uint32_t key_perm_t; + +struct key; + +#ifdef CONFIG_KEYS + +#undef KEY_DEBUGGING + +#define KEY_USR_VIEW 0x00010000 /* user can view a key's attributes */ +#define KEY_USR_READ 0x00020000 /* user can read key payload / view keyring */ +#define KEY_USR_WRITE 0x00040000 /* user can update key payload / add link to keyring */ +#define KEY_USR_SEARCH 0x00080000 /* user can find a key in search / search a keyring */ +#define KEY_USR_LINK 0x00100000 /* user can create a link to a key/keyring */ +#define KEY_USR_ALL 0x001f0000 + +#define KEY_GRP_VIEW 0x00000100 /* group permissions... */ +#define KEY_GRP_READ 0x00000200 +#define KEY_GRP_WRITE 0x00000400 +#define KEY_GRP_SEARCH 0x00000800 +#define KEY_GRP_LINK 0x00001000 +#define KEY_GRP_ALL 0x00001f00 + +#define KEY_OTH_VIEW 0x00000001 /* third party permissions... */ +#define KEY_OTH_READ 0x00000002 +#define KEY_OTH_WRITE 0x00000004 +#define KEY_OTH_SEARCH 0x00000008 +#define KEY_OTH_LINK 0x00000010 +#define KEY_OTH_ALL 0x0000001f + +struct seq_file; +struct user_struct; + +struct key_type; +struct key_owner; +struct keyring_list; +struct keyring_name; + +/*****************************************************************************/ +/* + * authentication token / access credential / keyring + * - types of key include: + * - keyrings + * - disk encryption IDs + * - Kerberos TGTs and tickets + */ +struct key { + atomic_t usage; /* number of references */ + key_serial_t serial; /* key serial number */ + struct rb_node serial_node; + struct key_type *type; /* type of key */ + rwlock_t lock; /* examination vs change lock */ + struct rw_semaphore sem; /* change vs change sem */ + struct key_user *user; /* owner of this key */ + time_t expiry; /* time at which key expires (or 0) */ + uid_t uid; + gid_t gid; + key_perm_t perm; /* access permissions */ + unsigned short quotalen; /* length added to quota */ + unsigned short datalen; /* payload data length */ + unsigned short flags; /* status flags (change with lock writelocked) */ +#define KEY_FLAG_INSTANTIATED 0x00000001 /* set if key has been instantiated */ +#define KEY_FLAG_DEAD 0x00000002 /* set if key type has been deleted */ +#define KEY_FLAG_REVOKED 0x00000004 /* set if key had been revoked */ +#define KEY_FLAG_IN_QUOTA 0x00000008 /* set if key consumes quota */ +#define KEY_FLAG_USER_CONSTRUCT 0x00000010 /* set if key is being constructed in userspace */ +#define KEY_FLAG_NEGATIVE 0x00000020 /* set if key is negative */ + +#ifdef KEY_DEBUGGING + unsigned magic; +#define KEY_DEBUG_MAGIC 0x18273645u +#define KEY_DEBUG_MAGIC_X 0xf8e9dacbu +#endif + + /* the description string + * - this is used to match a key against search criteria + * - this should be a printable string + * - eg: for krb5 AFS, this might be "afs@REDHAT.COM" + */ + char *description; + + /* type specific data + * - this is used by the keyring type to index the name + */ + union { + struct list_head link; + } type_data; + + /* key data + * - this is used to hold the data actually used in cryptography or + * whatever + */ + union { + unsigned long value; + void *data; + struct keyring_list *subscriptions; + } payload; +}; + +/*****************************************************************************/ +/* + * kernel managed key type definition + */ +struct key_type { + /* name of the type */ + const char *name; + + /* default payload length for quota precalculation (optional) + * - this can be used instead of calling key_payload_reserve(), that + * function only needs to be called if the real datalen is different + */ + size_t def_datalen; + + /* instantiate a key of this type + * - this method should call key_payload_reserve() to determine if the + * user's quota will hold the payload + */ + int (*instantiate)(struct key *key, const void *data, size_t datalen); + + /* duplicate a key of this type (optional) + * - the source key will be locked against change + * - the new description will be attached + * - the quota will have been adjusted automatically from + * source->quotalen + */ + int (*duplicate)(struct key *key, const struct key *source); + + /* update a key of this type (optional) + * - this method should call key_payload_reserve() to recalculate the + * quota consumption + * - the key must be locked against read when modifying + */ + int (*update)(struct key *key, const void *data, size_t datalen); + + /* match a key against a description */ + int (*match)(const struct key *key, const void *desc); + + /* clear the data from a key (optional) */ + void (*destroy)(struct key *key); + + /* describe a key */ + void (*describe)(const struct key *key, struct seq_file *p); + + /* read a key's data (optional) + * - permission checks will be done by the caller + * - the key's semaphore will be readlocked by the caller + * - should return the amount of data that could be read, no matter how + * much is copied into the buffer + * - shouldn't do the copy if the buffer is NULL + */ + long (*read)(const struct key *key, char __user *buffer, size_t buflen); + + /* internal fields */ + struct list_head link; /* link in types list */ +}; + +extern struct key_type key_type_keyring; + +extern int register_key_type(struct key_type *ktype); +extern void unregister_key_type(struct key_type *ktype); + +extern struct key *key_alloc(struct key_type *type, + const char *desc, + uid_t uid, gid_t gid, key_perm_t perm, + int not_in_quota); +extern int key_payload_reserve(struct key *key, size_t datalen); +extern int key_instantiate_and_link(struct key *key, + const void *data, + size_t datalen, + struct key *keyring); +extern int key_negate_and_link(struct key *key, + unsigned timeout, + struct key *keyring); +extern void key_revoke(struct key *key); +extern void key_put(struct key *key); + +static inline struct key *key_get(struct key *key) +{ + if (key) + atomic_inc(&key->usage); + return key; +} + +extern struct key *request_key(struct key_type *type, + const char *description, + const char *callout_info); + +extern int key_validate(struct key *key); + +extern struct key *key_create_or_update(struct key *keyring, + const char *type, + const char *description, + const void *payload, + size_t plen, + int not_in_quota); + +extern int key_update(struct key *key, + const void *payload, + size_t plen); + +extern int key_link(struct key *keyring, + struct key *key); + +extern int key_unlink(struct key *keyring, + struct key *key); + +extern struct key *keyring_alloc(const char *description, uid_t uid, gid_t gid, + int not_in_quota, struct key *dest); + +extern int keyring_clear(struct key *keyring); + +extern struct key *keyring_search(struct key *keyring, + struct key_type *type, + const char *description); + +extern struct key *search_process_keyrings(struct key_type *type, + const char *description); + +extern int keyring_add_key(struct key *keyring, + struct key *key); + +extern struct key *key_lookup(key_serial_t id); + +#define key_serial(key) ((key) ? (key)->serial : 0) + +/* + * the userspace interface + */ +extern struct key root_user_keyring, root_session_keyring; +extern int alloc_uid_keyring(struct user_struct *user); +extern void switch_uid_keyring(struct user_struct *new_user); +extern int copy_keys(unsigned long clone_flags, struct task_struct *tsk); +extern void exit_keys(struct task_struct *tsk); +extern int suid_keys(struct task_struct *tsk); +extern int exec_keys(struct task_struct *tsk); +extern void key_fsuid_changed(struct task_struct *tsk); +extern void key_fsgid_changed(struct task_struct *tsk); + +#else /* CONFIG_KEYS */ + +#define key_validate(k) 0 +#define key_serial(k) 0 +#define key_get(k) NULL +#define key_put(k) do { } while(0) +#define alloc_uid_keyring(u) 0 +#define switch_uid_keyring(u) do { } while(0) +#define copy_keys(f,t) 0 +#define exit_keys(t) do { } while(0) +#define suid_keys(t) do { } while(0) +#define exec_keys(t) do { } while(0) +#define key_fsuid_changed(t) do { } while(0) +#define key_fsgid_changed(t) do { } while(0) + +#endif /* CONFIG_KEYS */ +#endif /* __KERNEL__ */ +#endif /* _LINUX_KEY_H */ diff --git a/include/linux/keyctl.h b/include/linux/keyctl.h new file mode 100644 index 000000000000..381dedc370a3 --- /dev/null +++ b/include/linux/keyctl.h @@ -0,0 +1,39 @@ +/* keyctl.h: keyctl command IDs + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#ifndef _LINUX_KEYCTL_H +#define _LINUX_KEYCTL_H + +/* special process keyring shortcut IDs */ +#define KEY_SPEC_THREAD_KEYRING -1 /* - key ID for thread-specific keyring */ +#define KEY_SPEC_PROCESS_KEYRING -2 /* - key ID for process-specific keyring */ +#define KEY_SPEC_SESSION_KEYRING -3 /* - key ID for session-specific keyring */ +#define KEY_SPEC_USER_KEYRING -4 /* - key ID for UID-specific keyring */ +#define KEY_SPEC_USER_SESSION_KEYRING -5 /* - key ID for UID-session keyring */ +#define KEY_SPEC_GROUP_KEYRING -6 /* - key ID for GID-specific keyring */ + +/* keyctl commands */ +#define KEYCTL_GET_KEYRING_ID 0 /* ask for a keyring's ID */ +#define KEYCTL_JOIN_SESSION_KEYRING 1 /* join or start named session keyring */ +#define KEYCTL_UPDATE 2 /* update a key */ +#define KEYCTL_REVOKE 3 /* revoke a key */ +#define KEYCTL_CHOWN 4 /* set ownership of a key */ +#define KEYCTL_SETPERM 5 /* set perms on a key */ +#define KEYCTL_DESCRIBE 6 /* describe a key */ +#define KEYCTL_CLEAR 7 /* clear contents of a keyring */ +#define KEYCTL_LINK 8 /* link a key into a keyring */ +#define KEYCTL_UNLINK 9 /* unlink a key from a keyring */ +#define KEYCTL_SEARCH 10 /* search for a key in a keyring */ +#define KEYCTL_READ 11 /* read a key or keyring's contents */ +#define KEYCTL_INSTANTIATE 12 /* instantiate a partially constructed key */ +#define KEYCTL_NEGATE 13 /* negate a partially constructed key */ + +#endif /* _LINUX_KEYCTL_H */ diff --git a/include/linux/prctl.h b/include/linux/prctl.h index 54333c98e532..edb036b43597 100644 --- a/include/linux/prctl.h +++ b/include/linux/prctl.h @@ -49,7 +49,6 @@ # define PR_TIMING_TIMESTAMP 1 /* Accurate timestamp based process timing */ - #define PR_SET_NAME 15 /* Set process name */ #endif /* _LINUX_PRCTL_H */ diff --git a/include/linux/sched.h b/include/linux/sched.h index dc3f297a726d..bbcd869e9304 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -358,6 +358,11 @@ struct user_struct { unsigned long mq_bytes; /* How many bytes can be allocated to mqueue? */ unsigned long locked_shm; /* How many pages of mlocked shm ? */ +#ifdef CONFIG_KEYS + struct key *uid_keyring; /* UID specific keyring */ + struct key *session_keyring; /* UID's default session keyring */ +#endif + /* Hash table maintenance information */ struct list_head uidhash_list; uid_t uid; @@ -611,6 +616,11 @@ struct task_struct { kernel_cap_t cap_effective, cap_inheritable, cap_permitted; unsigned keep_capabilities:1; struct user_struct *user; +#ifdef CONFIG_KEYS + struct key *session_keyring; /* keyring inherited over fork */ + struct key *process_keyring; /* keyring private to this process (CLONE_THREAD) */ + struct key *thread_keyring; /* keyring private to this thread */ +#endif unsigned short used_math; char comm[16]; /* file system info */ @@ -644,7 +654,7 @@ struct task_struct { /* Thread group tracking */ u32 parent_exec_id; u32 self_exec_id; -/* Protection of (de-)allocation: mm, files, fs, tty */ +/* Protection of (de-)allocation: mm, files, fs, tty, keyrings */ spinlock_t alloc_lock; /* Protection of proc_dentry: nesting proc_lock, dcache_lock, write_lock_irq(&tasklist_lock); */ spinlock_t proc_lock; @@ -977,8 +987,8 @@ static inline int thread_group_empty(task_t *p) extern void unhash_process(struct task_struct *p); /* - * Protects ->fs, ->files, ->mm, ->ptrace, ->group_info, ->comm and - * synchronises with wait4(). + * Protects ->fs, ->files, ->mm, ->ptrace, ->group_info, ->comm, keyring + * subscriptions and synchronises with wait4(). Also used in procfs. * * Nests both inside and outside of read_lock(&tasklist_lock). * It must not be nested with write_lock_irq(&tasklist_lock), diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h index 2a8c7faf2dcc..bc93606badbc 100644 --- a/include/linux/syscalls.h +++ b/include/linux/syscalls.h @@ -61,6 +61,7 @@ struct mq_attr; #include #include #include +#include asmlinkage long sys_time(int __user *tloc); asmlinkage long sys_stime(time_t __user *tptr); @@ -492,4 +493,18 @@ asmlinkage long sys_uselib(const char __user *library); asmlinkage long sys_setaltroot(const char __user *altroot); asmlinkage long sys_ni_syscall(void); +asmlinkage long sys_add_key(const char __user *_type, + const char __user *_description, + const void __user *_payload, + size_t plen, + key_serial_t destringid); + +asmlinkage long sys_request_key(const char __user *_type, + const char __user *_description, + const char __user *_callout_info, + key_serial_t destringid); + +asmlinkage long sys_keyctl(int cmd, unsigned long arg2, unsigned long arg3, + unsigned long arg4, unsigned long arg5); + #endif diff --git a/kernel/exit.c b/kernel/exit.c index 55d853392524..e242c22dee36 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -816,6 +817,7 @@ asmlinkage NORET_TYPE void do_exit(long code) __exit_fs(tsk); exit_namespace(tsk); exit_thread(); + exit_keys(tsk); if (tsk->signal->leader) disassociate_ctty(1); diff --git a/kernel/fork.c b/kernel/fork.c index 3020dccc548f..2502791f0ba4 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -1019,6 +1020,10 @@ static task_t *copy_process(unsigned long clone_flags, } #endif + p->tgid = p->pid; + if (clone_flags & CLONE_THREAD) + p->tgid = current->tgid; + if ((retval = security_task_alloc(p))) goto bad_fork_cleanup_policy; if ((retval = audit_alloc(p))) @@ -1036,8 +1041,10 @@ static task_t *copy_process(unsigned long clone_flags, goto bad_fork_cleanup_sighand; if ((retval = copy_mm(clone_flags, p))) goto bad_fork_cleanup_signal; - if ((retval = copy_namespace(clone_flags, p))) + if ((retval = copy_keys(clone_flags, p))) goto bad_fork_cleanup_mm; + if ((retval = copy_namespace(clone_flags, p))) + goto bad_fork_cleanup_keys; retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs); if (retval) goto bad_fork_cleanup_namespace; @@ -1071,7 +1078,6 @@ static task_t *copy_process(unsigned long clone_flags, * Ok, make it visible to the rest of the system. * We dont wake it up yet. */ - p->tgid = p->pid; p->group_leader = p; INIT_LIST_HEAD(&p->ptrace_children); INIT_LIST_HEAD(&p->ptrace_list); @@ -1119,7 +1125,6 @@ static task_t *copy_process(unsigned long clone_flags, retval = -EAGAIN; goto bad_fork_cleanup_namespace; } - p->tgid = current->tgid; p->group_leader = current->group_leader; if (current->signal->group_stop_count > 0) { @@ -1159,6 +1164,8 @@ fork_out: bad_fork_cleanup_namespace: exit_namespace(p); +bad_fork_cleanup_keys: + exit_keys(p); bad_fork_cleanup_mm: if (p->mm) mmput(p->mm); diff --git a/kernel/sys.c b/kernel/sys.c index a95e3900dc1e..e6dbc2940751 100644 --- a/kernel/sys.c +++ b/kernel/sys.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -282,6 +283,9 @@ cond_syscall(sys_set_mempolicy) cond_syscall(compat_mbind) cond_syscall(compat_get_mempolicy) cond_syscall(compat_set_mempolicy) +cond_syscall(sys_add_key) +cond_syscall(sys_request_key) +cond_syscall(sys_keyctl) /* arch-specific weak syscall entries */ cond_syscall(sys_pciconfig_read) @@ -605,6 +609,7 @@ asmlinkage long sys_setregid(gid_t rgid, gid_t egid) current->fsgid = new_egid; current->egid = new_egid; current->gid = new_rgid; + key_fsgid_changed(current); return 0; } @@ -642,6 +647,8 @@ asmlinkage long sys_setgid(gid_t gid) } else return -EPERM; + + key_fsgid_changed(current); return 0; } @@ -730,6 +737,8 @@ asmlinkage long sys_setreuid(uid_t ruid, uid_t euid) current->suid = current->euid; current->fsuid = current->euid; + key_fsuid_changed(current); + return security_task_post_setuid(old_ruid, old_euid, old_suid, LSM_SETID_RE); } @@ -775,6 +784,8 @@ asmlinkage long sys_setuid(uid_t uid) current->fsuid = current->euid = uid; current->suid = new_suid; + key_fsuid_changed(current); + return security_task_post_setuid(old_ruid, old_euid, old_suid, LSM_SETID_ID); } @@ -821,6 +832,8 @@ asmlinkage long sys_setresuid(uid_t ruid, uid_t euid, uid_t suid) if (suid != (uid_t) -1) current->suid = suid; + key_fsuid_changed(current); + return security_task_post_setuid(old_ruid, old_euid, old_suid, LSM_SETID_RES); } @@ -870,6 +883,8 @@ asmlinkage long sys_setresgid(gid_t rgid, gid_t egid, gid_t sgid) current->gid = rgid; if (sgid != (gid_t) -1) current->sgid = sgid; + + key_fsgid_changed(current); return 0; } @@ -911,6 +926,8 @@ asmlinkage long sys_setfsuid(uid_t uid) current->fsuid = uid; } + key_fsuid_changed(current); + security_task_post_setuid(old_fsuid, (uid_t)-1, (uid_t)-1, LSM_SETID_FS); return old_fsuid; @@ -937,6 +954,7 @@ asmlinkage long sys_setfsgid(gid_t gid) wmb(); } current->fsgid = gid; + key_fsgid_changed(current); } return old_fsgid; } @@ -1669,7 +1687,7 @@ asmlinkage long sys_umask(int mask) asmlinkage long sys_prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5) { - int error; + long error; int sig; error = security_task_prctl(option, arg2, arg3, arg4, arg5); diff --git a/kernel/user.c b/kernel/user.c index 523175afeecd..693487dc940e 100644 --- a/kernel/user.c +++ b/kernel/user.c @@ -12,6 +12,7 @@ #include #include #include +#include /* * UID task count cache, to get fast user lookup in "alloc_uid" @@ -34,6 +35,10 @@ struct user_struct root_user = { .sigpending = ATOMIC_INIT(0), .mq_bytes = 0, .locked_shm = 0, +#ifdef CONFIG_KEYS + .uid_keyring = &root_user_keyring, + .session_keyring = &root_session_keyring, +#endif }; /* @@ -87,6 +92,8 @@ void free_uid(struct user_struct *up) { if (up && atomic_dec_and_lock(&up->__count, &uidhash_lock)) { uid_hash_remove(up); + key_put(up->uid_keyring); + key_put(up->session_keyring); kmem_cache_free(uid_cachep, up); spin_unlock(&uidhash_lock); } @@ -116,6 +123,11 @@ struct user_struct * alloc_uid(uid_t uid) new->mq_bytes = 0; new->locked_shm = 0; + if (alloc_uid_keyring(new) < 0) { + kmem_cache_free(uid_cachep, new); + return NULL; + } + /* * Before adding this, check whether we raced * on adding the same user already.. @@ -123,6 +135,8 @@ struct user_struct * alloc_uid(uid_t uid) spin_lock(&uidhash_lock); up = uid_hash_find(uid, hashent); if (up) { + key_put(new->uid_keyring); + key_put(new->session_keyring); kmem_cache_free(uid_cachep, new); } else { uid_hash_insert(new, hashent); @@ -146,8 +160,10 @@ void switch_uid(struct user_struct *new_user) old_user = current->user; atomic_inc(&new_user->processes); atomic_dec(&old_user->processes); + switch_uid_keyring(new_user); current->user = new_user; free_uid(old_user); + suid_keys(current); } diff --git a/security/Kconfig b/security/Kconfig index ddde53ba6234..8a35e4d52c8b 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -4,6 +4,35 @@ menu "Security options" +config KEYS + bool "Enable access key retention support" + help + This option provides support for retaining authentication tokens and + access keys in the kernel. + + It also includes provision of methods by which such keys might be + associated with a process so that network filesystems, encryption + support and the like can find them. + + Furthermore, a special type of key is available that acts as keyring: + a searchable sequence of keys. Each process is equipped with access + to five standard keyrings: UID-specific, GID-specific, session, + process and thread. + + If you are unsure as to whether this is required, answer N. + +config KEYS_DEBUG_PROC_KEYS + bool "Enable the /proc/keys file by which all keys may be viewed" + depends on KEYS + help + This option turns on support for the /proc/keys file through which + all the keys on the system can be listed. + + This option is a slight security risk in that it makes it possible + for anyone to see all the keys on the system. Normally the manager + pretends keys that are inaccessible to a process don't exist as far + as that process is concerned. + config SECURITY bool "Enable different security models" help diff --git a/security/Makefile b/security/Makefile index 3686a1bb324a..473861ea657a 100644 --- a/security/Makefile +++ b/security/Makefile @@ -2,6 +2,7 @@ # Makefile for the kernel security code # +obj-$(CONFIG_KEYS) += keys/ subdir-$(CONFIG_SECURITY_SELINUX) += selinux # if we don't select a security model, use the default capabilities diff --git a/security/keys/Makefile b/security/keys/Makefile new file mode 100644 index 000000000000..bd6500dbab0e --- /dev/null +++ b/security/keys/Makefile @@ -0,0 +1,13 @@ +# +# Makefile for key management +# + +obj-y := \ + key.o \ + keyring.o \ + keyctl.o \ + process_keys.o \ + user_defined.o \ + request_key.o + +obj-$(CONFIG_PROC_FS) += proc.o diff --git a/security/keys/internal.h b/security/keys/internal.h new file mode 100644 index 000000000000..e68e0c7ee29e --- /dev/null +++ b/security/keys/internal.h @@ -0,0 +1,109 @@ +/* internal.h: authentication token and access key management internal defs + * + * Copyright (C) 2003 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#ifndef _INTERNAL_H +#define _INTERNAL_H + +#include +#include + +extern struct key_type key_type_dead; +extern struct key_type key_type_user; + +/*****************************************************************************/ +/* + * keep track of keys for a user + * - this needs to be separate to user_struct to avoid a refcount-loop + * (user_struct pins some keyrings which pin this struct) + * - this also keeps track of keys under request from userspace for this UID + */ +struct key_user { + struct rb_node node; + struct list_head consq; /* construction queue */ + spinlock_t lock; + atomic_t usage; /* for accessing qnkeys & qnbytes */ + atomic_t nkeys; /* number of keys */ + atomic_t nikeys; /* number of instantiated keys */ + uid_t uid; + int qnkeys; /* number of keys allocated to this user */ + int qnbytes; /* number of bytes allocated to this user */ +}; + +#define KEYQUOTA_MAX_KEYS 100 +#define KEYQUOTA_MAX_BYTES 10000 +#define KEYQUOTA_LINK_BYTES 4 /* a link in a keyring is worth 4 bytes */ + +extern struct rb_root key_user_tree; +extern spinlock_t key_user_lock; +extern struct key_user root_key_user; + +extern struct key_user *key_user_lookup(uid_t uid); +extern void key_user_put(struct key_user *user); + + + +extern struct rb_root key_serial_tree; +extern spinlock_t key_serial_lock; +extern struct semaphore key_alloc_sem; +extern struct rw_semaphore key_construction_sem; +extern wait_queue_head_t request_key_conswq; + + +extern void keyring_publish_name(struct key *keyring); + +extern int __key_link(struct key *keyring, struct key *key); + +extern struct key *__keyring_search_one(struct key *keyring, + const struct key_type *type, + const char *description, + key_perm_t perm); + +typedef int (*key_match_func_t)(const struct key *, const void *); + +extern struct key *keyring_search_aux(struct key *keyring, + struct key_type *type, + const void *description, + key_match_func_t match); + +extern struct key *search_process_keyrings_aux(struct key_type *type, + const void *description, + key_match_func_t match); + +extern struct key *find_keyring_by_name(const char *name, key_serial_t bound); + +extern int install_thread_keyring(struct task_struct *tsk); + + +/* + * debugging key validation + */ +#ifdef KEY_DEBUGGING +static void __key_check(const struct key *key) +{ + printk("__key_check: key %p {%08x} should be {%08x}\n", + key, key->magic, KEY_DEBUG_MAGIC); + BUG(); +} + + +static inline void key_check(const struct key *key) +{ + if (key && (IS_ERR(key) || key->magic != KEY_DEBUG_MAGIC)) + __key_check(key); +} + +#else + +#define key_check(key) do {} while(0) + +#endif + +#endif /* _INTERNAL_H */ diff --git a/security/keys/key.c b/security/keys/key.c new file mode 100644 index 000000000000..da9fc0aea739 --- /dev/null +++ b/security/keys/key.c @@ -0,0 +1,1039 @@ +/* key.c: basic authentication token and access key management + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include "internal.h" + +static kmem_cache_t *key_jar; +static key_serial_t key_serial_next = 3; +struct rb_root key_serial_tree; /* tree of keys indexed by serial */ +spinlock_t key_serial_lock = SPIN_LOCK_UNLOCKED; + +struct rb_root key_user_tree; /* tree of quota records indexed by UID */ +spinlock_t key_user_lock = SPIN_LOCK_UNLOCKED; + +static LIST_HEAD(key_types_list); +static DECLARE_RWSEM(key_types_sem); + +static void key_cleanup(void *data); +static DECLARE_WORK(key_cleanup_task, key_cleanup, NULL); + +/* we serialise key instantiation and link */ +DECLARE_RWSEM(key_construction_sem); + +/* any key who's type gets unegistered will be re-typed to this */ +struct key_type key_type_dead = { + .name = "dead", +}; + +/*****************************************************************************/ +/* + * get the key quota record for a user, allocating a new record if one doesn't + * already exist + */ +struct key_user *key_user_lookup(uid_t uid) +{ + struct key_user *candidate = NULL, *user; + struct rb_node *parent = NULL; + struct rb_node **p = &key_user_tree.rb_node; + + try_again: + spin_lock(&key_user_lock); + + /* search the tree for a user record with a matching UID */ + while (*p) { + parent = *p; + user = rb_entry(parent, struct key_user, node); + + if (uid < user->uid) + p = &(*p)->rb_left; + else if (uid > user->uid) + p = &(*p)->rb_right; + else + goto found; + } + + /* if we get here, we failed to find a match in the tree */ + if (!candidate) { + /* allocate a candidate user record if we don't already have + * one */ + spin_unlock(&key_user_lock); + + user = NULL; + candidate = kmalloc(sizeof(struct key_user), GFP_KERNEL); + if (unlikely(!candidate)) + goto out; + + /* the allocation may have scheduled, so we need to repeat the + * search lest someone else added the record whilst we were + * asleep */ + goto try_again; + } + + /* if we get here, then the user record still hadn't appeared on the + * second pass - so we use the candidate record */ + atomic_set(&candidate->usage, 1); + atomic_set(&candidate->nkeys, 0); + atomic_set(&candidate->nikeys, 0); + candidate->uid = uid; + candidate->qnkeys = 0; + candidate->qnbytes = 0; + spin_lock_init(&candidate->lock); + INIT_LIST_HEAD(&candidate->consq); + + rb_link_node(&candidate->node, parent, p); + rb_insert_color(&candidate->node, &key_user_tree); + spin_unlock(&key_user_lock); + user = candidate; + goto out; + + /* okay - we found a user record for this UID */ + found: + atomic_inc(&user->usage); + spin_unlock(&key_user_lock); + if (candidate) + kfree(candidate); + out: + return user; + +} /* end key_user_lookup() */ + +/*****************************************************************************/ +/* + * dispose of a user structure + */ +void key_user_put(struct key_user *user) +{ + if (atomic_dec_and_lock(&user->usage, &key_user_lock)) { + rb_erase(&user->node, &key_user_tree); + spin_unlock(&key_user_lock); + + kfree(user); + } + +} /* end key_user_put() */ + +/*****************************************************************************/ +/* + * insert a key with a fixed serial number + */ +static void __init __key_insert_serial(struct key *key) +{ + struct rb_node *parent, **p; + struct key *xkey; + + parent = NULL; + p = &key_serial_tree.rb_node; + + while (*p) { + parent = *p; + xkey = rb_entry(parent, struct key, serial_node); + + if (key->serial < xkey->serial) + p = &(*p)->rb_left; + else if (key->serial > xkey->serial) + p = &(*p)->rb_right; + else + BUG(); + } + + /* we've found a suitable hole - arrange for this key to occupy it */ + rb_link_node(&key->serial_node, parent, p); + rb_insert_color(&key->serial_node, &key_serial_tree); + +} /* end __key_insert_serial() */ + +/*****************************************************************************/ +/* + * assign a key the next unique serial number + * - we work through all the serial numbers between 2 and 2^31-1 in turn and + * then wrap + */ +static inline void key_alloc_serial(struct key *key) +{ + struct rb_node *parent, **p; + struct key *xkey; + + spin_lock(&key_serial_lock); + + /* propose a likely serial number and look for a hole for it in the + * serial number tree */ + key->serial = key_serial_next; + if (key->serial < 3) + key->serial = 3; + key_serial_next = key->serial + 1; + + parent = NULL; + p = &key_serial_tree.rb_node; + + while (*p) { + parent = *p; + xkey = rb_entry(parent, struct key, serial_node); + + if (key->serial < xkey->serial) + p = &(*p)->rb_left; + else if (key->serial > xkey->serial) + p = &(*p)->rb_right; + else + goto serial_exists; + } + goto insert_here; + + /* we found a key with the proposed serial number - walk the tree from + * that point looking for the next unused serial number */ + serial_exists: + for (;;) { + key->serial = key_serial_next; + if (key->serial < 2) + key->serial = 2; + key_serial_next = key->serial + 1; + + if (!parent->rb_parent) + p = &key_serial_tree.rb_node; + else if (parent->rb_parent->rb_left == parent) + p = &parent->rb_parent->rb_left; + else + p = &parent->rb_parent->rb_right; + + parent = rb_next(parent); + if (!parent) + break; + + xkey = rb_entry(parent, struct key, serial_node); + if (key->serial < xkey->serial) + goto insert_here; + } + + /* we've found a suitable hole - arrange for this key to occupy it */ + insert_here: + rb_link_node(&key->serial_node, parent, p); + rb_insert_color(&key->serial_node, &key_serial_tree); + + spin_unlock(&key_serial_lock); + +} /* end key_alloc_serial() */ + +/*****************************************************************************/ +/* + * allocate a key of the specified type + * - update the user's quota to reflect the existence of the key + * - called from a key-type operation with key_types_sem read-locked by either + * key_create_or_update() or by key_duplicate(); this prevents unregistration + * of the key type + * - upon return the key is as yet uninstantiated; the caller needs to either + * instantiate the key or discard it before returning + */ +struct key *key_alloc(struct key_type *type, const char *desc, + uid_t uid, gid_t gid, key_perm_t perm, + int not_in_quota) +{ + struct key_user *user = NULL; + struct key *key; + size_t desclen, quotalen; + + key = ERR_PTR(-EINVAL); + if (!desc || !*desc) + goto error; + + desclen = strlen(desc) + 1; + quotalen = desclen + type->def_datalen; + + /* get hold of the key tracking for this user */ + user = key_user_lookup(uid); + if (!user) + goto no_memory_1; + + /* check that the user's quota permits allocation of another key and + * its description */ + if (!not_in_quota) { + spin_lock(&user->lock); + if (user->qnkeys + 1 >= KEYQUOTA_MAX_KEYS && + user->qnbytes + quotalen >= KEYQUOTA_MAX_BYTES + ) + goto no_quota; + + user->qnkeys++; + user->qnbytes += quotalen; + spin_unlock(&user->lock); + } + + /* allocate and initialise the key and its description */ + key = kmem_cache_alloc(key_jar, SLAB_KERNEL); + if (!key) + goto no_memory_2; + + if (desc) { + key->description = kmalloc(desclen, GFP_KERNEL); + if (!key->description) + goto no_memory_3; + + memcpy(key->description, desc, desclen); + } + + atomic_set(&key->usage, 1); + rwlock_init(&key->lock); + init_rwsem(&key->sem); + key->type = type; + key->user = user; + key->quotalen = quotalen; + key->datalen = type->def_datalen; + key->uid = uid; + key->gid = gid; + key->perm = perm; + key->flags = 0; + key->expiry = 0; + key->payload.data = NULL; + + if (!not_in_quota) + key->flags |= KEY_FLAG_IN_QUOTA; + + memset(&key->type_data, 0, sizeof(key->type_data)); + +#ifdef KEY_DEBUGGING + key->magic = KEY_DEBUG_MAGIC; +#endif + + /* publish the key by giving it a serial number */ + atomic_inc(&user->nkeys); + key_alloc_serial(key); + + error: + return key; + + no_memory_3: + kmem_cache_free(key_jar, key); + no_memory_2: + if (!not_in_quota) { + spin_lock(&user->lock); + user->qnkeys--; + user->qnbytes -= quotalen; + spin_unlock(&user->lock); + } + key_user_put(user); + no_memory_1: + key = ERR_PTR(-ENOMEM); + goto error; + + no_quota: + spin_unlock(&user->lock); + key_user_put(user); + key = ERR_PTR(-EDQUOT); + goto error; + +} /* end key_alloc() */ + +EXPORT_SYMBOL(key_alloc); + +/*****************************************************************************/ +/* + * reserve an amount of quota for the key's payload + */ +int key_payload_reserve(struct key *key, size_t datalen) +{ + int delta = (int) datalen - key->datalen; + int ret = 0; + + key_check(key); + + /* contemplate the quota adjustment */ + if (delta != 0 && key->flags & KEY_FLAG_IN_QUOTA) { + spin_lock(&key->user->lock); + + if (delta > 0 && + key->user->qnbytes + delta > KEYQUOTA_MAX_BYTES + ) { + ret = -EDQUOT; + } + else { + key->user->qnbytes += delta; + key->quotalen += delta; + } + spin_unlock(&key->user->lock); + } + + /* change the recorded data length if that didn't generate an error */ + if (ret == 0) + key->datalen = datalen; + + return ret; + +} /* end key_payload_reserve() */ + +EXPORT_SYMBOL(key_payload_reserve); + +/*****************************************************************************/ +/* + * instantiate a key and link it into the target keyring atomically + * - called with the target keyring's semaphore writelocked + */ +static int __key_instantiate_and_link(struct key *key, + const void *data, + size_t datalen, + struct key *keyring) +{ + int ret, awaken; + + key_check(key); + key_check(keyring); + + awaken = 0; + ret = -EBUSY; + + down_write(&key_construction_sem); + + /* can't instantiate twice */ + if (!(key->flags & KEY_FLAG_INSTANTIATED)) { + /* instantiate the key */ + ret = key->type->instantiate(key, data, datalen); + + if (ret == 0) { + /* mark the key as being instantiated */ + write_lock(&key->lock); + + atomic_inc(&key->user->nikeys); + key->flags |= KEY_FLAG_INSTANTIATED; + + if (key->flags & KEY_FLAG_USER_CONSTRUCT) { + key->flags &= ~KEY_FLAG_USER_CONSTRUCT; + awaken = 1; + } + + write_unlock(&key->lock); + + /* and link it into the destination keyring */ + if (keyring) + ret = __key_link(keyring, key); + } + } + + up_write(&key_construction_sem); + + /* wake up anyone waiting for a key to be constructed */ + if (awaken) + wake_up_all(&request_key_conswq); + + return ret; + +} /* end __key_instantiate_and_link() */ + +/*****************************************************************************/ +/* + * instantiate a key and link it into the target keyring atomically + */ +int key_instantiate_and_link(struct key *key, + const void *data, + size_t datalen, + struct key *keyring) +{ + int ret; + + if (keyring) + down_write(&keyring->sem); + + ret = __key_instantiate_and_link(key, data, datalen, keyring); + + if (keyring) + up_write(&keyring->sem); + + return ret; +} /* end key_instantiate_and_link() */ + +EXPORT_SYMBOL(key_instantiate_and_link); + +/*****************************************************************************/ +/* + * negatively instantiate a key and link it into the target keyring atomically + */ +int key_negate_and_link(struct key *key, + unsigned timeout, + struct key *keyring) +{ + struct timespec now; + int ret, awaken; + + key_check(key); + key_check(keyring); + + awaken = 0; + ret = -EBUSY; + + if (keyring) + down_write(&keyring->sem); + + down_write(&key_construction_sem); + + /* can't instantiate twice */ + if (!(key->flags & KEY_FLAG_INSTANTIATED)) { + /* mark the key as being negatively instantiated */ + write_lock(&key->lock); + + atomic_inc(&key->user->nikeys); + key->flags |= KEY_FLAG_INSTANTIATED | KEY_FLAG_NEGATIVE; + now = current_kernel_time(); + key->expiry = now.tv_sec + timeout; + + if (key->flags & KEY_FLAG_USER_CONSTRUCT) { + key->flags &= ~KEY_FLAG_USER_CONSTRUCT; + awaken = 1; + } + + write_unlock(&key->lock); + ret = 0; + + /* and link it into the destination keyring */ + if (keyring) + ret = __key_link(keyring, key); + } + + up_write(&key_construction_sem); + + if (keyring) + up_write(&keyring->sem); + + /* wake up anyone waiting for a key to be constructed */ + if (awaken) + wake_up_all(&request_key_conswq); + + return ret; + +} /* end key_negate_and_link() */ + +EXPORT_SYMBOL(key_negate_and_link); + +/*****************************************************************************/ +/* + * do cleaning up in process context so that we don't have to disable + * interrupts all over the place + */ +static void key_cleanup(void *data) +{ + struct rb_node *_n; + struct key *key; + + go_again: + /* look for a dead key in the tree */ + spin_lock(&key_serial_lock); + + for (_n = rb_first(&key_serial_tree); _n; _n = rb_next(_n)) { + key = rb_entry(_n, struct key, serial_node); + + if (atomic_read(&key->usage) == 0) + goto found_dead_key; + } + + spin_unlock(&key_serial_lock); + return; + + found_dead_key: + /* we found a dead key - once we've removed it from the tree, we can + * drop the lock */ + rb_erase(&key->serial_node, &key_serial_tree); + spin_unlock(&key_serial_lock); + + /* deal with the user's key tracking and quota */ + if (key->flags & KEY_FLAG_IN_QUOTA) { + spin_lock(&key->user->lock); + key->user->qnkeys--; + key->user->qnbytes -= key->quotalen; + spin_unlock(&key->user->lock); + } + + atomic_dec(&key->user->nkeys); + if (key->flags & KEY_FLAG_INSTANTIATED) + atomic_dec(&key->user->nikeys); + + key_user_put(key->user); + + /* now throw away the key memory */ + if (key->type->destroy) + key->type->destroy(key); + + kfree(key->description); + +#ifdef KEY_DEBUGGING + key->magic = KEY_DEBUG_MAGIC_X; +#endif + kmem_cache_free(key_jar, key); + + /* there may, of course, be more than one key to destroy */ + goto go_again; + +} /* end key_cleanup() */ + +/*****************************************************************************/ +/* + * dispose of a reference to a key + * - when all the references are gone, we schedule the cleanup task to come and + * pull it out of the tree in definite process context + */ +void key_put(struct key *key) +{ + if (key) { + key_check(key); + + if (atomic_dec_and_test(&key->usage)) + schedule_work(&key_cleanup_task); + } + +} /* end key_put() */ + +EXPORT_SYMBOL(key_put); + +/*****************************************************************************/ +/* + * find a key by its serial number + */ +struct key *key_lookup(key_serial_t id) +{ + struct rb_node *n; + struct key *key; + + spin_lock(&key_serial_lock); + + /* search the tree for the specified key */ + n = key_serial_tree.rb_node; + while (n) { + key = rb_entry(n, struct key, serial_node); + + if (id < key->serial) + n = n->rb_left; + else if (id > key->serial) + n = n->rb_right; + else + goto found; + } + + spin_unlock(&key_serial_lock); + + not_found: + key = ERR_PTR(-ENOKEY); + goto error; + + found: + /* pretent doesn't exist if it's dead */ + if (atomic_read(&key->usage) == 0 || + (key->flags & KEY_FLAG_DEAD) || + key->type == &key_type_dead) + goto not_found; + + /* this races with key_put(), but that doesn't matter since key_put() + * doesn't actually change the key + */ + atomic_inc(&key->usage); + + spin_unlock(&key_serial_lock); + error: + return key; + +} /* end key_lookup() */ + +/*****************************************************************************/ +/* + * find and lock the specified key type against removal + * - we return with the sem readlocked + */ +struct key_type *key_type_lookup(const char *type) +{ + struct key_type *ktype; + + down_read(&key_types_sem); + + /* look up the key type to see if it's one of the registered kernel + * types */ + list_for_each_entry(ktype, &key_types_list, link) { + if (strcmp(ktype->name, type) == 0) + goto found_kernel_type; + } + + up_read(&key_types_sem); + ktype = ERR_PTR(-ENOKEY); + + found_kernel_type: + return ktype; + +} /* end key_type_lookup() */ + +/*****************************************************************************/ +/* + * unlock a key type + */ +void key_type_put(struct key_type *ktype) +{ + up_read(&key_types_sem); + +} /* end key_type_put() */ + +/*****************************************************************************/ +/* + * attempt to update an existing key + * - the key has an incremented refcount + * - we need to put the key if we get an error + */ +static inline struct key *__key_update(struct key *key, const void *payload, + size_t plen) +{ + int ret; + + /* need write permission on the key to update it */ + ret = -EACCES; + if (!key_permission(key, KEY_WRITE)) + goto error; + + ret = -EEXIST; + if (!key->type->update) + goto error; + + down_write(&key->sem); + + ret = key->type->update(key, payload, plen); + + if (ret == 0) { + /* updating a negative key instantiates it */ + write_lock(&key->lock); + key->flags &= ~KEY_FLAG_NEGATIVE; + write_unlock(&key->lock); + } + + up_write(&key->sem); + + if (ret < 0) + goto error; + out: + return key; + + error: + key_put(key); + key = ERR_PTR(ret); + goto out; + +} /* end __key_update() */ + +/*****************************************************************************/ +/* + * search the specified keyring for a key of the same description; if one is + * found, update it, otherwise add a new one + */ +struct key *key_create_or_update(struct key *keyring, + const char *type, + const char *description, + const void *payload, + size_t plen, + int not_in_quota) +{ + struct key_type *ktype; + struct key *key = NULL; + key_perm_t perm; + int ret; + + key_check(keyring); + + /* look up the key type to see if it's one of the registered kernel + * types */ + ktype = key_type_lookup(type); + if (IS_ERR(ktype)) { + key = ERR_PTR(-ENODEV); + goto error; + } + + ret = -EINVAL; + if (!ktype->match || !ktype->instantiate) + goto error_2; + + /* search for an existing key of the same type and description in the + * destination keyring + */ + down_write(&keyring->sem); + + key = __keyring_search_one(keyring, ktype, description, 0); + if (!IS_ERR(key)) + goto found_matching_key; + + /* if we're going to allocate a new key, we're going to have to modify + * the keyring */ + ret = -EACCES; + if (!key_permission(keyring, KEY_WRITE)) + goto error_3; + + /* decide on the permissions we want */ + perm = KEY_USR_VIEW | KEY_USR_SEARCH | KEY_USR_LINK; + + if (ktype->read) + perm |= KEY_USR_READ; + + if (ktype == &key_type_keyring || ktype->update) + perm |= KEY_USR_WRITE; + + /* allocate a new key */ + key = key_alloc(ktype, description, current->fsuid, current->fsgid, + perm, not_in_quota); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error_3; + } + + /* instantiate it and link it into the target keyring */ + ret = __key_instantiate_and_link(key, payload, plen, keyring); + if (ret < 0) { + key_put(key); + key = ERR_PTR(ret); + } + + error_3: + up_write(&keyring->sem); + error_2: + key_type_put(ktype); + error: + return key; + + found_matching_key: + /* we found a matching key, so we're going to try to update it + * - we can drop the locks first as we have the key pinned + */ + up_write(&keyring->sem); + key_type_put(ktype); + + key = __key_update(key, payload, plen); + goto error; + +} /* end key_create_or_update() */ + +EXPORT_SYMBOL(key_create_or_update); + +/*****************************************************************************/ +/* + * update a key + */ +int key_update(struct key *key, const void *payload, size_t plen) +{ + int ret; + + key_check(key); + + /* the key must be writable */ + ret = -EACCES; + if (!key_permission(key, KEY_WRITE)) + goto error; + + /* attempt to update it if supported */ + ret = -EOPNOTSUPP; + if (key->type->update) { + down_write(&key->sem); + ret = key->type->update(key, payload, plen); + + if (ret == 0) { + /* updating a negative key instantiates it */ + write_lock(&key->lock); + key->flags &= ~KEY_FLAG_NEGATIVE; + write_unlock(&key->lock); + } + + up_write(&key->sem); + } + + error: + return ret; + +} /* end key_update() */ + +EXPORT_SYMBOL(key_update); + +/*****************************************************************************/ +/* + * duplicate a key, potentially with a revised description + * - must be supported by the keytype (keyrings for instance can be duplicated) + */ +struct key *key_duplicate(struct key *source, const char *desc) +{ + struct key *key; + int ret; + + key_check(source); + + if (!desc) + desc = source->description; + + down_read(&key_types_sem); + + ret = -EINVAL; + if (!source->type->duplicate) + goto error; + + /* allocate and instantiate a key */ + key = key_alloc(source->type, desc, current->fsuid, current->fsgid, + source->perm, 0); + if (IS_ERR(key)) + goto error_k; + + down_read(&source->sem); + ret = key->type->duplicate(key, source); + up_read(&source->sem); + if (ret < 0) + goto error2; + + atomic_inc(&key->user->nikeys); + + write_lock(&key->lock); + key->flags |= KEY_FLAG_INSTANTIATED; + write_unlock(&key->lock); + + error_k: + up_read(&key_types_sem); + out: + return key; + + error2: + key_put(key); + error: + up_read(&key_types_sem); + key = ERR_PTR(ret); + goto out; + +} /* end key_duplicate() */ + +/*****************************************************************************/ +/* + * revoke a key + */ +void key_revoke(struct key *key) +{ + key_check(key); + + /* make sure no one's trying to change or use the key when we mark + * it */ + down_write(&key->sem); + write_lock(&key->lock); + key->flags |= KEY_FLAG_REVOKED; + write_unlock(&key->lock); + up_write(&key->sem); + +} /* end key_revoke() */ + +EXPORT_SYMBOL(key_revoke); + +/*****************************************************************************/ +/* + * register a type of key + */ +int register_key_type(struct key_type *ktype) +{ + struct key_type *p; + int ret; + + ret = -EEXIST; + down_write(&key_types_sem); + + /* disallow key types with the same name */ + list_for_each_entry(p, &key_types_list, link) { + if (strcmp(p->name, ktype->name) == 0) + goto out; + } + + /* store the type */ + list_add(&ktype->link, &key_types_list); + ret = 0; + + out: + up_write(&key_types_sem); + return ret; + +} /* end register_key_type() */ + +EXPORT_SYMBOL(register_key_type); + +/*****************************************************************************/ +/* + * unregister a type of key + */ +void unregister_key_type(struct key_type *ktype) +{ + struct rb_node *_n; + struct key *key; + + down_write(&key_types_sem); + + /* withdraw the key type */ + list_del_init(&ktype->link); + + /* need to withdraw all keys of this type */ + spin_lock(&key_serial_lock); + + for (_n = rb_first(&key_serial_tree); _n; _n = rb_next(_n)) { + key = rb_entry(_n, struct key, serial_node); + + if (key->type != ktype) + continue; + + write_lock(&key->lock); + key->type = &key_type_dead; + write_unlock(&key->lock); + + /* there shouldn't be anyone looking at the description or + * payload now */ + if (ktype->destroy) + ktype->destroy(key); + memset(&key->payload, 0xbd, sizeof(key->payload)); + } + + spin_unlock(&key_serial_lock); + up_write(&key_types_sem); + +} /* end unregister_key_type() */ + +EXPORT_SYMBOL(unregister_key_type); + +/*****************************************************************************/ +/* + * initialise the key management stuff + */ +static int __init key_init(void) +{ + /* allocate a slab in which we can store keys */ + key_jar = kmem_cache_create("key_jar", sizeof(struct key), + 0, SLAB_HWCACHE_ALIGN, NULL, NULL); + if (!key_jar) + panic("Cannot create key jar\n"); + + /* add the special key types */ + list_add_tail(&key_type_keyring.link, &key_types_list); + list_add_tail(&key_type_dead.link, &key_types_list); + list_add_tail(&key_type_user.link, &key_types_list); + + /* record the root user tracking */ + rb_link_node(&root_key_user.node, + NULL, + &key_user_tree.rb_node); + + rb_insert_color(&root_key_user.node, + &key_user_tree); + + /* record root's user standard keyrings */ + key_check(&root_user_keyring); + key_check(&root_session_keyring); + + __key_insert_serial(&root_user_keyring); + __key_insert_serial(&root_session_keyring); + + keyring_publish_name(&root_user_keyring); + keyring_publish_name(&root_session_keyring); + + /* link the two root keyrings together */ + key_link(&root_session_keyring, &root_user_keyring); + + return 0; + +} /* end key_init() */ + +subsys_initcall(key_init); diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c new file mode 100644 index 000000000000..2045b24615fd --- /dev/null +++ b/security/keys/keyctl.c @@ -0,0 +1,991 @@ +/* keyctl.c: userspace keyctl operations + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "internal.h" + +/*****************************************************************************/ +/* + * extract the description of a new key from userspace and either add it as a + * new key to the specified keyring or update a matching key in that keyring + * - the keyring must be writable + * - returns the new key's serial number + * - implements add_key() + */ +asmlinkage long sys_add_key(const char __user *_type, + const char __user *_description, + const void __user *_payload, + size_t plen, + key_serial_t ringid) +{ + struct key *keyring, *key; + char type[32], *description; + void *payload; + long dlen, ret; + + ret = -EINVAL; + if (plen > 32767) + goto error; + + /* draw all the data into kernel space */ + ret = strncpy_from_user(type, _type, sizeof(type) - 1); + if (ret < 0) + goto error; + type[31] = '\0'; + + ret = -EFAULT; + dlen = strnlen_user(_description, PAGE_SIZE - 1); + if (dlen <= 0) + goto error; + + ret = -EINVAL; + if (dlen > PAGE_SIZE - 1) + goto error; + + ret = -ENOMEM; + description = kmalloc(dlen + 1, GFP_KERNEL); + if (!description) + goto error; + + ret = -EFAULT; + if (copy_from_user(description, _description, dlen + 1) != 0) + goto error2; + + /* pull the payload in if one was supplied */ + payload = NULL; + + if (_payload) { + ret = -ENOMEM; + payload = kmalloc(plen, GFP_KERNEL); + if (!payload) + goto error2; + + ret = -EFAULT; + if (copy_from_user(payload, _payload, plen) != 0) + goto error3; + } + + /* find the target keyring (which must be writable) */ + keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error3; + } + + /* create or update the requested key and add it to the target + * keyring */ + key = key_create_or_update(keyring, type, description, + payload, plen, 0); + if (!IS_ERR(key)) { + ret = key->serial; + key_put(key); + } + else { + ret = PTR_ERR(key); + } + + key_put(keyring); + error3: + kfree(payload); + error2: + kfree(description); + error: + return ret; + +} /* end sys_add_key() */ + +/*****************************************************************************/ +/* + * search the process keyrings for a matching key + * - nested keyrings may also be searched if they have Search permission + * - if a key is found, it will be attached to the destination keyring if + * there's one specified + * - /sbin/request-key will be invoked if _callout_info is non-NULL + * - the _callout_info string will be passed to /sbin/request-key + * - if the _callout_info string is empty, it will be rendered as "-" + * - implements request_key() + */ +asmlinkage long sys_request_key(const char __user *_type, + const char __user *_description, + const char __user *_callout_info, + key_serial_t destringid) +{ + struct key_type *ktype; + struct key *key, *dest; + char type[32], *description, *callout_info; + long dlen, ret; + + /* pull the type into kernel space */ + ret = strncpy_from_user(type, _type, sizeof(type) - 1); + if (ret < 0) + goto error; + type[31] = '\0'; + + /* pull the description into kernel space */ + ret = -EFAULT; + dlen = strnlen_user(_description, PAGE_SIZE - 1); + if (dlen <= 0) + goto error; + + ret = -EINVAL; + if (dlen > PAGE_SIZE - 1) + goto error; + + ret = -ENOMEM; + description = kmalloc(dlen + 1, GFP_KERNEL); + if (!description) + goto error; + + ret = -EFAULT; + if (copy_from_user(description, _description, dlen + 1) != 0) + goto error2; + + /* pull the callout info into kernel space */ + callout_info = NULL; + if (_callout_info) { + ret = -EFAULT; + dlen = strnlen_user(_callout_info, PAGE_SIZE - 1); + if (dlen <= 0) + goto error2; + + ret = -EINVAL; + if (dlen > PAGE_SIZE - 1) + goto error2; + + ret = -ENOMEM; + callout_info = kmalloc(dlen + 1, GFP_KERNEL); + if (!callout_info) + goto error2; + + ret = -EFAULT; + if (copy_from_user(callout_info, _callout_info, dlen + 1) != 0) + goto error3; + } + + /* get the destination keyring if specified */ + dest = NULL; + if (destringid) { + dest = lookup_user_key(destringid, 1, 0, KEY_WRITE); + if (IS_ERR(dest)) { + ret = PTR_ERR(dest); + goto error3; + } + } + + /* find the key type */ + ktype = key_type_lookup(type); + if (IS_ERR(ktype)) { + ret = PTR_ERR(ktype); + goto error4; + } + + /* do the search */ + key = request_key(ktype, description, callout_info); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error5; + } + + /* link the resulting key to the destination keyring */ + if (dest) { + ret = key_link(dest, key); + if (ret < 0) + goto error6; + } + + ret = key->serial; + + error6: + key_put(key); + error5: + key_type_put(ktype); + error4: + key_put(dest); + error3: + kfree(callout_info); + error2: + kfree(description); + error: + return ret; + +} /* end sys_request_key() */ + +/*****************************************************************************/ +/* + * get the ID of the specified process keyring + * - the keyring must have search permission to be found + * - implements keyctl(KEYCTL_GET_KEYRING_ID) + */ +static long keyctl_get_keyring_ID(key_serial_t id, int create) +{ + struct key *key; + long ret; + + key = lookup_user_key(id, create, 0, KEY_SEARCH); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + ret = key->serial; + key_put(key); + error: + return ret; + +} /* end keyctl_get_keyring_ID() */ + +/*****************************************************************************/ +/* + * join the session keyring + * - implements keyctl(KEYCTL_JOIN_SESSION_KEYRING) + */ +static long keyctl_join_session_keyring(const char __user *_name) +{ + char *name; + long nlen, ret; + + /* fetch the name from userspace */ + name = NULL; + if (_name) { + ret = -EFAULT; + nlen = strnlen_user(_name, PAGE_SIZE - 1); + if (nlen <= 0) + goto error; + + ret = -EINVAL; + if (nlen > PAGE_SIZE - 1) + goto error; + + ret = -ENOMEM; + name = kmalloc(nlen + 1, GFP_KERNEL); + if (!name) + goto error; + + ret = -EFAULT; + if (copy_from_user(name, _name, nlen + 1) != 0) + goto error2; + } + + /* join the session */ + ret = join_session_keyring(name); + + error2: + kfree(name); + error: + return ret; + +} /* end keyctl_join_session_keyring() */ + +/*****************************************************************************/ +/* + * update a key's data payload + * - the key must be writable + * - implements keyctl(KEYCTL_UPDATE) + */ +static long keyctl_update_key(key_serial_t id, + const void __user *_payload, + size_t plen) +{ + struct key *key; + void *payload; + long ret; + + ret = -EINVAL; + if (plen > PAGE_SIZE) + goto error; + + /* pull the payload in if one was supplied */ + payload = NULL; + if (_payload) { + ret = -ENOMEM; + payload = kmalloc(plen, GFP_KERNEL); + if (!payload) + goto error; + + ret = -EFAULT; + if (copy_from_user(payload, _payload, plen) != 0) + goto error2; + } + + /* find the target key (which must be writable) */ + key = lookup_user_key(id, 0, 0, KEY_WRITE); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error2; + } + + /* update the key */ + ret = key_update(key, payload, plen); + + key_put(key); + error2: + kfree(payload); + error: + return ret; + +} /* end keyctl_update_key() */ + +/*****************************************************************************/ +/* + * revoke a key + * - the key must be writable + * - implements keyctl(KEYCTL_REVOKE) + */ +static long keyctl_revoke_key(key_serial_t id) +{ + struct key *key; + long ret; + + key = lookup_user_key(id, 0, 0, KEY_WRITE); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + key_revoke(key); + ret = 0; + + key_put(key); + error: + return 0; + +} /* end keyctl_revoke_key() */ + +/*****************************************************************************/ +/* + * clear the specified process keyring + * - the keyring must be writable + * - implements keyctl(KEYCTL_CLEAR) + */ +static long keyctl_keyring_clear(key_serial_t ringid) +{ + struct key *keyring; + long ret; + + keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + + ret = keyring_clear(keyring); + + key_put(keyring); + error: + return ret; + +} /* end keyctl_keyring_clear() */ + +/*****************************************************************************/ +/* + * link a key into a keyring + * - the keyring must be writable + * - the key must be linkable + * - implements keyctl(KEYCTL_LINK) + */ +static long keyctl_keyring_link(key_serial_t id, key_serial_t ringid) +{ + struct key *keyring, *key; + long ret; + + keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + + key = lookup_user_key(id, 1, 0, KEY_LINK); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error2; + } + + ret = key_link(keyring, key); + + key_put(key); + error2: + key_put(keyring); + error: + return ret; + +} /* end keyctl_keyring_link() */ + +/*****************************************************************************/ +/* + * unlink the first attachment of a key from a keyring + * - the keyring must be writable + * - we don't need any permissions on the key + * - implements keyctl(KEYCTL_UNLINK) + */ +static long keyctl_keyring_unlink(key_serial_t id, key_serial_t ringid) +{ + struct key *keyring, *key; + long ret; + + keyring = lookup_user_key(ringid, 0, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + + key = lookup_user_key(id, 0, 0, 0); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error2; + } + + ret = key_unlink(keyring, key); + + key_put(key); + error2: + key_put(keyring); + error: + return ret; + +} /* end keyctl_keyring_unlink() */ + +/*****************************************************************************/ +/* + * describe a user key + * - the key must have view permission + * - if there's a buffer, we place up to buflen bytes of data into it + * - unless there's an error, we return the amount of description available, + * irrespective of how much we may have copied + * - the description is formatted thus: + * type;uid;gid;perm;description + * - implements keyctl(KEYCTL_DESCRIBE) + */ +static long keyctl_describe_key(key_serial_t keyid, + char __user *buffer, + size_t buflen) +{ + struct key *key; + char *tmpbuf; + long ret; + + key = lookup_user_key(keyid, 0, 1, KEY_VIEW); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + /* calculate how much description we're going to return */ + ret = -ENOMEM; + tmpbuf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!tmpbuf) + goto error2; + + ret = snprintf(tmpbuf, PAGE_SIZE - 1, + "%s;%d;%d;%06x;%s", + key->type->name, + key->uid, + key->gid, + key->perm, + key->description ? key->description :"" + ); + + /* include a NUL char at the end of the data */ + if (ret > PAGE_SIZE - 1) + ret = PAGE_SIZE - 1; + tmpbuf[ret] = 0; + ret++; + + /* consider returning the data */ + if (buffer && buflen > 0) { + if (buflen > ret) + buflen = ret; + + if (copy_to_user(buffer, tmpbuf, buflen) != 0) + ret = -EFAULT; + } + + kfree(tmpbuf); + error2: + key_put(key); + error: + return ret; + +} /* end keyctl_describe_key() */ + +/*****************************************************************************/ +/* + * search the specified keyring for a matching key + * - the start keyring must be searchable + * - nested keyrings may also be searched if they are searchable + * - only keys with search permission may be found + * - if a key is found, it will be attached to the destination keyring if + * there's one specified + * - implements keyctl(KEYCTL_SEARCH) + */ +static long keyctl_keyring_search(key_serial_t ringid, + const char __user *_type, + const char __user *_description, + key_serial_t destringid) +{ + struct key_type *ktype; + struct key *keyring, *key, *dest; + char type[32], *description; + long dlen, ret; + + /* pull the type and description into kernel space */ + ret = strncpy_from_user(type, _type, sizeof(type) - 1); + if (ret < 0) + goto error; + type[31] = '\0'; + + ret = -EFAULT; + dlen = strnlen_user(_description, PAGE_SIZE - 1); + if (dlen <= 0) + goto error; + + ret = -EINVAL; + if (dlen > PAGE_SIZE - 1) + goto error; + + ret = -ENOMEM; + description = kmalloc(dlen + 1, GFP_KERNEL); + if (!description) + goto error; + + ret = -EFAULT; + if (copy_from_user(description, _description, dlen + 1) != 0) + goto error2; + + /* get the keyring at which to begin the search */ + keyring = lookup_user_key(ringid, 0, 0, KEY_SEARCH); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error2; + } + + /* get the destination keyring if specified */ + dest = NULL; + if (destringid) { + dest = lookup_user_key(destringid, 1, 0, KEY_WRITE); + if (IS_ERR(dest)) { + ret = PTR_ERR(dest); + goto error3; + } + } + + /* find the key type */ + ktype = key_type_lookup(type); + if (IS_ERR(ktype)) { + ret = PTR_ERR(ktype); + goto error4; + } + + /* do the search */ + key = keyring_search(keyring, ktype, description); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + + /* treat lack or presence of a negative key the same */ + if (ret == -EAGAIN) + ret = -ENOKEY; + goto error5; + } + + /* link the resulting key to the destination keyring if we can */ + if (dest) { + ret = -EACCES; + if (!key_permission(key, KEY_LINK)) + goto error6; + + ret = key_link(dest, key); + if (ret < 0) + goto error6; + } + + ret = key->serial; + + error6: + key_put(key); + error5: + key_type_put(ktype); + error4: + key_put(dest); + error3: + key_put(keyring); + error2: + kfree(description); + error: + return ret; + +} /* end keyctl_keyring_search() */ + +/*****************************************************************************/ +/* + * see if the key we're looking at is the target key + */ +static int keyctl_read_key_same(const struct key *key, const void *target) +{ + return key == target; + +} /* end keyctl_read_key_same() */ + +/*****************************************************************************/ +/* + * read a user key's payload + * - the keyring must be readable or the key must be searchable from the + * process's keyrings + * - if there's a buffer, we place up to buflen bytes of data into it + * - unless there's an error, we return the amount of data in the key, + * irrespective of how much we may have copied + * - implements keyctl(KEYCTL_READ) + */ +static long keyctl_read_key(key_serial_t keyid, + char __user *buffer, + size_t buflen) +{ + struct key *key, *skey; + long ret; + + /* find the key first */ + key = lookup_user_key(keyid, 0, 0, 0); + if (!IS_ERR(key)) { + /* see if we can read it directly */ + if (key_permission(key, KEY_READ)) + goto can_read_key; + + /* can't; see if it's searchable from this process's + * keyrings */ + ret = -ENOKEY; + if (key_permission(key, KEY_SEARCH)) { + /* okay - we do have search permission on the key + * itself, but do we have the key? */ + skey = search_process_keyrings_aux(key->type, key, + keyctl_read_key_same); + if (!IS_ERR(skey)) + goto can_read_key2; + } + + goto error2; + } + + ret = -ENOKEY; + goto error; + + /* the key is probably readable - now try to read it */ + can_read_key2: + key_put(skey); + can_read_key: + ret = key_validate(key); + if (ret == 0) { + ret = -EOPNOTSUPP; + if (key->type->read) { + /* read the data with the semaphore held (since we + * might sleep) */ + down_read(&key->sem); + ret = key->type->read(key, buffer, buflen); + up_read(&key->sem); + } + } + + error2: + key_put(key); + error: + return ret; + +} /* end keyctl_read_key() */ + +/*****************************************************************************/ +/* + * change the ownership of a key + * - the keyring owned by the changer + * - if the uid or gid is -1, then that parameter is not changed + * - implements keyctl(KEYCTL_CHOWN) + */ +static long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid) +{ + struct key *key; + long ret; + + ret = 0; + if (uid == (uid_t) -1 && gid == (gid_t) -1) + goto error; + + key = lookup_user_key(id, 1, 1, 0); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + /* make the changes with the locks held to prevent chown/chown races */ + ret = -EACCES; + down_write(&key->sem); + write_lock(&key->lock); + + if (!capable(CAP_SYS_ADMIN)) { + /* only the sysadmin can chown a key to some other UID */ + if (uid != (uid_t) -1 && key->uid != uid) + goto no_access; + + /* only the sysadmin can set the key's GID to a group other + * than one of those that the current process subscribes to */ + if (gid != (gid_t) -1 && gid != key->gid && !in_group_p(gid)) + goto no_access; + } + + /* change the UID (have to update the quotas) */ + if (uid != (uid_t) -1 && uid != key->uid) { + /* don't support UID changing yet */ + ret = -EOPNOTSUPP; + goto no_access; + } + + /* change the GID */ + if (gid != (gid_t) -1) + key->gid = gid; + + ret = 0; + + no_access: + write_unlock(&key->lock); + up_write(&key->sem); + key_put(key); + error: + return ret; + +} /* end keyctl_chown_key() */ + +/*****************************************************************************/ +/* + * change the permission mask on a key + * - the keyring owned by the changer + * - implements keyctl(KEYCTL_SETPERM) + */ +static long keyctl_setperm_key(key_serial_t id, key_perm_t perm) +{ + struct key *key; + long ret; + + ret = -EINVAL; + if (perm & ~(KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL)) + goto error; + + key = lookup_user_key(id, 1, 1, 0); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + /* make the changes with the locks held to prevent chown/chmod + * races */ + ret = -EACCES; + down_write(&key->sem); + write_lock(&key->lock); + + /* if we're not the sysadmin, we can only chmod a key that we + * own */ + if (!capable(CAP_SYS_ADMIN) && key->uid != current->fsuid) + goto no_access; + + /* changing the permissions mask */ + key->perm = perm; + ret = 0; + + no_access: + write_unlock(&key->lock); + up_write(&key->sem); + key_put(key); + error: + return ret; + +} /* end keyctl_setperm_key() */ + +/*****************************************************************************/ +/* + * instantiate the key with the specified payload, and, if one is given, link + * the key into the keyring + */ +static long keyctl_instantiate_key(key_serial_t id, + const void __user *_payload, + size_t plen, + key_serial_t ringid) +{ + struct key *key, *keyring; + void *payload; + long ret; + + ret = -EINVAL; + if (plen > 32767) + goto error; + + /* pull the payload in if one was supplied */ + payload = NULL; + + if (_payload) { + ret = -ENOMEM; + payload = kmalloc(plen, GFP_KERNEL); + if (!payload) + goto error; + + ret = -EFAULT; + if (copy_from_user(payload, _payload, plen) != 0) + goto error2; + } + + /* find the target key (which must be writable) */ + key = lookup_user_key(id, 0, 1, KEY_WRITE); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error2; + } + + /* find the destination keyring if present (which must also be + * writable) */ + keyring = NULL; + if (ringid) { + keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error3; + } + } + + /* instantiate the key and link it into a keyring */ + ret = key_instantiate_and_link(key, payload, plen, keyring); + + key_put(keyring); + error3: + key_put(key); + error2: + kfree(payload); + error: + return ret; + +} /* end keyctl_instantiate_key() */ + +/*****************************************************************************/ +/* + * negatively instantiate the key with the given timeout (in seconds), and, if + * one is given, link the key into the keyring + */ +static long keyctl_negate_key(key_serial_t id, + unsigned timeout, + key_serial_t ringid) +{ + struct key *key, *keyring; + long ret; + + /* find the target key (which must be writable) */ + key = lookup_user_key(id, 0, 1, KEY_WRITE); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + /* find the destination keyring if present (which must also be + * writable) */ + keyring = NULL; + if (ringid) { + keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error2; + } + } + + /* instantiate the key and link it into a keyring */ + ret = key_negate_and_link(key, timeout, keyring); + + key_put(keyring); + error2: + key_put(key); + error: + return ret; + +} /* end keyctl_negate_key() */ + +/*****************************************************************************/ +/* + * the key control system call + * - currently invoked through prctl() + */ +asmlinkage long sys_keyctl(int option, unsigned long arg2, unsigned long arg3, + unsigned long arg4, unsigned long arg5) +{ + switch (option) { + case KEYCTL_GET_KEYRING_ID: + return keyctl_get_keyring_ID((key_serial_t) arg2, + (int) arg3); + + case KEYCTL_JOIN_SESSION_KEYRING: + return keyctl_join_session_keyring((const char __user *) arg3); + + case KEYCTL_UPDATE: + return keyctl_update_key((key_serial_t) arg2, + (const void __user *) arg3, + (size_t) arg4); + + case KEYCTL_REVOKE: + return keyctl_revoke_key((key_serial_t) arg2); + + case KEYCTL_DESCRIBE: + return keyctl_describe_key((key_serial_t) arg2, + (char __user *) arg3, + (unsigned) arg4); + + case KEYCTL_CLEAR: + return keyctl_keyring_clear((key_serial_t) arg2); + + case KEYCTL_LINK: + return keyctl_keyring_link((key_serial_t) arg2, + (key_serial_t) arg3); + + case KEYCTL_UNLINK: + return keyctl_keyring_unlink((key_serial_t) arg2, + (key_serial_t) arg3); + + case KEYCTL_SEARCH: + return keyctl_keyring_search((key_serial_t) arg2, + (const char __user *) arg3, + (const char __user *) arg4, + (key_serial_t) arg5); + + case KEYCTL_READ: + return keyctl_read_key((key_serial_t) arg2, + (char __user *) arg3, + (size_t) arg4); + + case KEYCTL_CHOWN: + return keyctl_chown_key((key_serial_t) arg2, + (uid_t) arg3, + (gid_t) arg4); + + case KEYCTL_SETPERM: + return keyctl_setperm_key((key_serial_t) arg2, + (key_perm_t) arg3); + + case KEYCTL_INSTANTIATE: + return keyctl_instantiate_key((key_serial_t) arg2, + (const void __user *) arg3, + (size_t) arg4, + (key_serial_t) arg5); + + case KEYCTL_NEGATE: + return keyctl_negate_key((key_serial_t) arg2, + (unsigned) arg3, + (key_serial_t) arg4); + + default: + return -EOPNOTSUPP; + } + +} /* end sys_keyctl() */ diff --git a/security/keys/keyring.c b/security/keys/keyring.c new file mode 100644 index 000000000000..98d4b0b817f7 --- /dev/null +++ b/security/keys/keyring.c @@ -0,0 +1,895 @@ +/* keyring.c: keyring handling + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "internal.h" + +/* + * when plumbing the depths of the key tree, this sets a hard limit set on how + * deep we're willing to go + */ +#define KEYRING_SEARCH_MAX_DEPTH 6 + +/* + * we keep all named keyrings in a hash to speed looking them up + */ +#define KEYRING_NAME_HASH_SIZE (1 << 5) + +static struct list_head keyring_name_hash[KEYRING_NAME_HASH_SIZE]; +static rwlock_t keyring_name_lock = RW_LOCK_UNLOCKED; + +static inline unsigned keyring_hash(const char *desc) +{ + unsigned bucket = 0; + + for (; *desc; desc++) + bucket += (unsigned char) *desc; + + return bucket & (KEYRING_NAME_HASH_SIZE - 1); +} + +/* + * the keyring type definition + */ +static int keyring_instantiate(struct key *keyring, + const void *data, size_t datalen); +static int keyring_duplicate(struct key *keyring, const struct key *source); +static int keyring_match(const struct key *keyring, const void *criterion); +static void keyring_destroy(struct key *keyring); +static void keyring_describe(const struct key *keyring, struct seq_file *m); +static long keyring_read(const struct key *keyring, + char __user *buffer, size_t buflen); + +struct key_type key_type_keyring = { + .name = "keyring", + .def_datalen = sizeof(struct keyring_list), + .instantiate = keyring_instantiate, + .duplicate = keyring_duplicate, + .match = keyring_match, + .destroy = keyring_destroy, + .describe = keyring_describe, + .read = keyring_read, +}; + +/* + * semaphore to serialise link/link calls to prevent two link calls in parallel + * introducing a cycle + */ +DECLARE_RWSEM(keyring_serialise_link_sem); + +/*****************************************************************************/ +/* + * publish the name of a keyring so that it can be found by name (if it has + * one) + */ +void keyring_publish_name(struct key *keyring) +{ + int bucket; + + if (keyring->description) { + bucket = keyring_hash(keyring->description); + + write_lock(&keyring_name_lock); + + if (!keyring_name_hash[bucket].next) + INIT_LIST_HEAD(&keyring_name_hash[bucket]); + + list_add_tail(&keyring->type_data.link, + &keyring_name_hash[bucket]); + + write_unlock(&keyring_name_lock); + } + +} /* end keyring_publish_name() */ + +/*****************************************************************************/ +/* + * initialise a keyring + * - we object if we were given any data + */ +static int keyring_instantiate(struct key *keyring, + const void *data, size_t datalen) +{ + int ret; + + ret = -EINVAL; + if (datalen == 0) { + /* make the keyring available by name if it has one */ + keyring_publish_name(keyring); + ret = 0; + } + + return ret; + +} /* end keyring_instantiate() */ + +/*****************************************************************************/ +/* + * duplicate the list of subscribed keys from a source keyring into this one + */ +static int keyring_duplicate(struct key *keyring, const struct key *source) +{ + struct keyring_list *sklist, *klist; + unsigned max; + size_t size; + int loop, ret; + + const unsigned limit = + (PAGE_SIZE - sizeof(*klist)) / sizeof(struct key); + + ret = 0; + sklist = source->payload.subscriptions; + + if (sklist && sklist->nkeys > 0) { + max = sklist->nkeys; + BUG_ON(max > limit); + + max = (max + 3) & ~3; + if (max > limit) + max = limit; + + ret = -ENOMEM; + size = sizeof(*klist) + sizeof(struct key) * max; + klist = kmalloc(size, GFP_KERNEL); + if (!klist) + goto error; + + klist->maxkeys = max; + klist->nkeys = sklist->nkeys; + memcpy(klist->keys, + sklist->keys, + sklist->nkeys * sizeof(struct key)); + + for (loop = klist->nkeys - 1; loop >= 0; loop--) + atomic_inc(&klist->keys[loop]->usage); + + keyring->payload.subscriptions = klist; + ret = 0; + } + + error: + return ret; + +} /* end keyring_duplicate() */ + +/*****************************************************************************/ +/* + * match keyrings on their name + */ +static int keyring_match(const struct key *keyring, const void *description) +{ + return keyring->description && + strcmp(keyring->description, description) == 0; + +} /* end keyring_match() */ + +/*****************************************************************************/ +/* + * dispose of the data dangling from the corpse of a keyring + */ +static void keyring_destroy(struct key *keyring) +{ + struct keyring_list *klist; + int loop; + + if (keyring->description) { + write_lock(&keyring_name_lock); + list_del(&keyring->type_data.link); + write_unlock(&keyring_name_lock); + } + + klist = keyring->payload.subscriptions; + if (klist) { + for (loop = klist->nkeys - 1; loop >= 0; loop--) + key_put(klist->keys[loop]); + kfree(klist); + } + +} /* end keyring_destroy() */ + +/*****************************************************************************/ +/* + * describe the keyring + */ +static void keyring_describe(const struct key *keyring, struct seq_file *m) +{ + struct keyring_list *klist; + + if (keyring->description) { + seq_puts(m, keyring->description); + } + else { + seq_puts(m, "[anon]"); + } + + klist = keyring->payload.subscriptions; + if (klist) + seq_printf(m, ": %u/%u", klist->nkeys, klist->maxkeys); + else + seq_puts(m, ": empty"); + +} /* end keyring_describe() */ + +/*****************************************************************************/ +/* + * read a list of key IDs from the keyring's contents + */ +static long keyring_read(const struct key *keyring, + char __user *buffer, size_t buflen) +{ + struct keyring_list *klist; + struct key *key; + size_t qty, tmp; + int loop, ret; + + ret = 0; + klist = keyring->payload.subscriptions; + + if (klist) { + /* calculate how much data we could return */ + qty = klist->nkeys * sizeof(key_serial_t); + + if (buffer && buflen > 0) { + if (buflen > qty) + buflen = qty; + + /* copy the IDs of the subscribed keys into the + * buffer */ + ret = -EFAULT; + + for (loop = 0; loop < klist->nkeys; loop++) { + key = klist->keys[loop]; + + tmp = sizeof(key_serial_t); + if (tmp > buflen) + tmp = buflen; + + if (copy_to_user(buffer, + &key->serial, + tmp) != 0) + goto error; + + buflen -= tmp; + if (buflen == 0) + break; + buffer += tmp; + } + } + + ret = qty; + } + + error: + return ret; + +} /* end keyring_read() */ + +/*****************************************************************************/ +/* + * allocate a keyring and link into the destination keyring + */ +struct key *keyring_alloc(const char *description, uid_t uid, gid_t gid, + int not_in_quota, struct key *dest) +{ + struct key *keyring; + int ret; + + keyring = key_alloc(&key_type_keyring, description, + uid, gid, KEY_USR_ALL, not_in_quota); + + if (!IS_ERR(keyring)) { + ret = key_instantiate_and_link(keyring, NULL, 0, dest); + if (ret < 0) { + key_put(keyring); + keyring = ERR_PTR(ret); + } + } + + return keyring; + +} /* end keyring_alloc() */ + +/*****************************************************************************/ +/* + * search the supplied keyring tree for a key that matches the criterion + * - perform a breadth-then-depth search up to the prescribed limit + * - we only find keys on which we have search permission + * - we use the supplied match function to see if the description (or other + * feature of interest) matches + * - we readlock the keyrings as we search down the tree + * - we return -EAGAIN if we didn't find any matching key + * - we return -ENOKEY if we only found negative matching keys + */ +struct key *keyring_search_aux(struct key *keyring, + struct key_type *type, + const void *description, + key_match_func_t match) +{ + struct { + struct key *keyring; + int kix; + } stack[KEYRING_SEARCH_MAX_DEPTH]; + + struct keyring_list *keylist; + struct timespec now; + struct key *key; + long err; + int sp, psp, kix; + + key_check(keyring); + + /* top keyring must have search permission to begin the search */ + key = ERR_PTR(-EACCES); + if (!key_permission(keyring, KEY_SEARCH)) + goto error; + + key = ERR_PTR(-ENOTDIR); + if (keyring->type != &key_type_keyring) + goto error; + + now = current_kernel_time(); + err = -EAGAIN; + sp = 0; + + /* start processing a new keyring */ + descend: + read_lock(&keyring->lock); + if (keyring->flags & KEY_FLAG_REVOKED) + goto not_this_keyring; + + keylist = keyring->payload.subscriptions; + if (!keylist) + goto not_this_keyring; + + /* iterate through the keys in this keyring first */ + for (kix = 0; kix < keylist->nkeys; kix++) { + key = keylist->keys[kix]; + + /* ignore keys not of this type */ + if (key->type != type) + continue; + + /* skip revoked keys and expired keys */ + if (key->flags & KEY_FLAG_REVOKED) + continue; + + if (key->expiry && now.tv_sec >= key->expiry) + continue; + + /* keys that don't match */ + if (!match(key, description)) + continue; + + /* key must have search permissions */ + if (!key_permission(key, KEY_SEARCH)) + continue; + + /* we set a different error code if we find a negative key */ + if (key->flags & KEY_FLAG_NEGATIVE) { + err = -ENOKEY; + continue; + } + + goto found; + } + + /* search through the keyrings nested in this one */ + kix = 0; + ascend: + while (kix < keylist->nkeys) { + key = keylist->keys[kix]; + if (key->type != &key_type_keyring) + goto next; + + /* recursively search nested keyrings + * - only search keyrings for which we have search permission + */ + if (sp >= KEYRING_SEARCH_MAX_DEPTH) + goto next; + + if (!key_permission(key, KEY_SEARCH)) + goto next; + + /* evade loops in the keyring tree */ + for (psp = 0; psp < sp; psp++) + if (stack[psp].keyring == keyring) + goto next; + + /* stack the current position */ + stack[sp].keyring = keyring; + stack[sp].kix = kix; + sp++; + + /* begin again with the new keyring */ + keyring = key; + goto descend; + + next: + kix++; + } + + /* the keyring we're looking at was disqualified or didn't contain a + * matching key */ + not_this_keyring: + read_unlock(&keyring->lock); + + if (sp > 0) { + /* resume the processing of a keyring higher up in the tree */ + sp--; + keyring = stack[sp].keyring; + keylist = keyring->payload.subscriptions; + kix = stack[sp].kix + 1; + goto ascend; + } + + key = ERR_PTR(err); + goto error; + + /* we found a viable match */ + found: + atomic_inc(&key->usage); + read_unlock(&keyring->lock); + + /* unwind the keyring stack */ + while (sp > 0) { + sp--; + read_unlock(&stack[sp].keyring->lock); + } + + key_check(key); + error: + return key; + +} /* end keyring_search_aux() */ + +/*****************************************************************************/ +/* + * search the supplied keyring tree for a key that matches the criterion + * - perform a breadth-then-depth search up to the prescribed limit + * - we only find keys on which we have search permission + * - we readlock the keyrings as we search down the tree + * - we return -EAGAIN if we didn't find any matching key + * - we return -ENOKEY if we only found negative matching keys + */ +struct key *keyring_search(struct key *keyring, + struct key_type *type, + const char *description) +{ + return keyring_search_aux(keyring, type, description, type->match); + +} /* end keyring_search() */ + +EXPORT_SYMBOL(keyring_search); + +/*****************************************************************************/ +/* + * search the given keyring only (no recursion) + * - keyring must be locked by caller + */ +struct key *__keyring_search_one(struct key *keyring, + const struct key_type *ktype, + const char *description, + key_perm_t perm) +{ + struct keyring_list *klist; + struct key *key; + int loop; + + klist = keyring->payload.subscriptions; + if (klist) { + for (loop = 0; loop < klist->nkeys; loop++) { + key = klist->keys[loop]; + + if (key->type == ktype && + key->type->match(key, description) && + key_permission(key, perm) && + !(key->flags & KEY_FLAG_REVOKED) + ) + goto found; + } + } + + key = ERR_PTR(-ENOKEY); + goto error; + + found: + atomic_inc(&key->usage); + error: + return key; + +} /* end __keyring_search_one() */ + +/*****************************************************************************/ +/* + * find a keyring with the specified name + * - all named keyrings are searched + * - only find keyrings with search permission for the process + * - only find keyrings with a serial number greater than the one specified + */ +struct key *find_keyring_by_name(const char *name, key_serial_t bound) +{ + struct key *keyring; + int bucket; + + keyring = ERR_PTR(-EINVAL); + if (!name) + goto error; + + bucket = keyring_hash(name); + + read_lock(&keyring_name_lock); + + if (keyring_name_hash[bucket].next) { + /* search this hash bucket for a keyring with a matching name + * that's readable and that hasn't been revoked */ + list_for_each_entry(keyring, + &keyring_name_hash[bucket], + type_data.link + ) { + if (keyring->flags & KEY_FLAG_REVOKED) + continue; + + if (strcmp(keyring->description, name) != 0) + continue; + + if (!key_permission(keyring, KEY_SEARCH)) + continue; + + /* found a potential candidate, but we still need to + * check the serial number */ + if (keyring->serial <= bound) + continue; + + /* we've got a match */ + atomic_inc(&keyring->usage); + read_unlock(&keyring_name_lock); + goto error; + } + } + + read_unlock(&keyring_name_lock); + keyring = ERR_PTR(-ENOKEY); + + error: + return keyring; + +} /* end find_keyring_by_name() */ + +/*****************************************************************************/ +/* + * see if a cycle will will be created by inserting acyclic tree B in acyclic + * tree A at the topmost level (ie: as a direct child of A) + * - since we are adding B to A at the top level, checking for cycles should + * just be a matter of seeing if node A is somewhere in tree B + */ +static int keyring_detect_cycle(struct key *A, struct key *B) +{ + struct { + struct key *subtree; + int kix; + } stack[KEYRING_SEARCH_MAX_DEPTH]; + + struct keyring_list *keylist; + struct key *subtree, *key; + int sp, kix, ret; + + ret = -EDEADLK; + if (A == B) + goto error; + + subtree = B; + sp = 0; + + /* start processing a new keyring */ + descend: + read_lock(&subtree->lock); + if (subtree->flags & KEY_FLAG_REVOKED) + goto not_this_keyring; + + keylist = subtree->payload.subscriptions; + if (!keylist) + goto not_this_keyring; + kix = 0; + + ascend: + /* iterate through the remaining keys in this keyring */ + for (; kix < keylist->nkeys; kix++) { + key = keylist->keys[kix]; + + if (key == A) + goto cycle_detected; + + /* recursively check nested keyrings */ + if (key->type == &key_type_keyring) { + if (sp >= KEYRING_SEARCH_MAX_DEPTH) + goto too_deep; + + /* stack the current position */ + stack[sp].subtree = subtree; + stack[sp].kix = kix; + sp++; + + /* begin again with the new keyring */ + subtree = key; + goto descend; + } + } + + /* the keyring we're looking at was disqualified or didn't contain a + * matching key */ + not_this_keyring: + read_unlock(&subtree->lock); + + if (sp > 0) { + /* resume the checking of a keyring higher up in the tree */ + sp--; + subtree = stack[sp].subtree; + keylist = subtree->payload.subscriptions; + kix = stack[sp].kix + 1; + goto ascend; + } + + ret = 0; /* no cycles detected */ + + error: + return ret; + + too_deep: + ret = -ELOOP; + goto error_unwind; + cycle_detected: + ret = -EDEADLK; + error_unwind: + read_unlock(&subtree->lock); + + /* unwind the keyring stack */ + while (sp > 0) { + sp--; + read_unlock(&stack[sp].subtree->lock); + } + + goto error; + +} /* end keyring_detect_cycle() */ + +/*****************************************************************************/ +/* + * link a key into to a keyring + * - must be called with the keyring's semaphore held + */ +int __key_link(struct key *keyring, struct key *key) +{ + struct keyring_list *klist, *nklist; + unsigned max; + size_t size; + int ret; + + ret = -EKEYREVOKED; + if (keyring->flags & KEY_FLAG_REVOKED) + goto error; + + ret = -ENOTDIR; + if (keyring->type != &key_type_keyring) + goto error; + + /* serialise link/link calls to prevent parallel calls causing a + * cycle when applied to two keyring in opposite orders */ + down_write(&keyring_serialise_link_sem); + + /* check that we aren't going to create a cycle adding one keyring to + * another */ + if (key->type == &key_type_keyring) { + ret = keyring_detect_cycle(keyring, key); + if (ret < 0) + goto error2; + } + + /* check that we aren't going to overrun the user's quota */ + ret = key_payload_reserve(keyring, + keyring->datalen + KEYQUOTA_LINK_BYTES); + if (ret < 0) + goto error2; + + klist = keyring->payload.subscriptions; + + if (klist && klist->nkeys < klist->maxkeys) { + /* there's sufficient slack space to add directly */ + atomic_inc(&key->usage); + + write_lock(&keyring->lock); + klist->keys[klist->nkeys++] = key; + write_unlock(&keyring->lock); + + ret = 0; + } + else { + /* grow the key list */ + max = 4; + if (klist) + max += klist->maxkeys; + + ret = -ENFILE; + size = sizeof(*klist) + sizeof(*key) * max; + if (size > PAGE_SIZE) + goto error3; + + ret = -ENOMEM; + nklist = kmalloc(size, GFP_KERNEL); + if (!nklist) + goto error3; + nklist->maxkeys = max; + nklist->nkeys = 0; + + if (klist) { + nklist->nkeys = klist->nkeys; + memcpy(nklist->keys, + klist->keys, + sizeof(struct key *) * klist->nkeys); + } + + /* add the key into the new space */ + atomic_inc(&key->usage); + + write_lock(&keyring->lock); + keyring->payload.subscriptions = nklist; + nklist->keys[nklist->nkeys++] = key; + write_unlock(&keyring->lock); + + /* dispose of the old keyring list */ + kfree(klist); + + ret = 0; + } + + error2: + up_write(&keyring_serialise_link_sem); + error: + return ret; + + error3: + /* undo the quota changes */ + key_payload_reserve(keyring, + keyring->datalen - KEYQUOTA_LINK_BYTES); + goto error2; + +} /* end __key_link() */ + +/*****************************************************************************/ +/* + * link a key to a keyring + */ +int key_link(struct key *keyring, struct key *key) +{ + int ret; + + key_check(keyring); + key_check(key); + + down_write(&keyring->sem); + ret = __key_link(keyring, key); + up_write(&keyring->sem); + + return ret; + +} /* end key_link() */ + +EXPORT_SYMBOL(key_link); + +/*****************************************************************************/ +/* + * unlink the first link to a key from a keyring + */ +int key_unlink(struct key *keyring, struct key *key) +{ + struct keyring_list *klist; + int loop, ret; + + key_check(keyring); + key_check(key); + + ret = -ENOTDIR; + if (keyring->type != &key_type_keyring) + goto error; + + down_write(&keyring->sem); + + klist = keyring->payload.subscriptions; + if (klist) { + /* search the keyring for the key */ + for (loop = 0; loop < klist->nkeys; loop++) + if (klist->keys[loop] == key) + goto key_is_present; + } + + up_write(&keyring->sem); + ret = -ENOENT; + goto error; + + key_is_present: + /* adjust the user's quota */ + key_payload_reserve(keyring, + keyring->datalen - KEYQUOTA_LINK_BYTES); + + /* shuffle down the key pointers + * - it might be worth shrinking the allocated memory, but that runs + * the risk of ENOMEM as we would have to copy + */ + write_lock(&keyring->lock); + + klist->nkeys--; + if (loop < klist->nkeys) + memcpy(&klist->keys[loop], + &klist->keys[loop + 1], + (klist->nkeys - loop) * sizeof(struct key *)); + + write_unlock(&keyring->lock); + + up_write(&keyring->sem); + key_put(key); + ret = 0; + + error: + return ret; + +} /* end key_unlink() */ + +EXPORT_SYMBOL(key_unlink); + +/*****************************************************************************/ +/* + * clear the specified process keyring + * - implements keyctl(KEYCTL_CLEAR) + */ +int keyring_clear(struct key *keyring) +{ + struct keyring_list *klist; + int loop, ret; + + ret = -ENOTDIR; + if (keyring->type == &key_type_keyring) { + /* detach the pointer block with the locks held */ + down_write(&keyring->sem); + + klist = keyring->payload.subscriptions; + if (klist) { + /* adjust the quota */ + key_payload_reserve(keyring, + sizeof(struct keyring_list)); + + write_lock(&keyring->lock); + keyring->payload.subscriptions = NULL; + write_unlock(&keyring->lock); + } + + up_write(&keyring->sem); + + /* free the keys after the locks have been dropped */ + if (klist) { + for (loop = klist->nkeys - 1; loop >= 0; loop--) + key_put(klist->keys[loop]); + + kfree(klist); + } + + ret = 0; + } + + return ret; + +} /* end keyring_clear() */ + +EXPORT_SYMBOL(keyring_clear); diff --git a/security/keys/proc.c b/security/keys/proc.c new file mode 100644 index 000000000000..91343b85c39c --- /dev/null +++ b/security/keys/proc.c @@ -0,0 +1,251 @@ +/* proc.c: proc files for key database enumeration + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "internal.h" + +#ifdef CONFIG_KEYS_DEBUG_PROC_KEYS +static int proc_keys_open(struct inode *inode, struct file *file); +static void *proc_keys_start(struct seq_file *p, loff_t *_pos); +static void *proc_keys_next(struct seq_file *p, void *v, loff_t *_pos); +static void proc_keys_stop(struct seq_file *p, void *v); +static int proc_keys_show(struct seq_file *m, void *v); + +static struct seq_operations proc_keys_ops = { + .start = proc_keys_start, + .next = proc_keys_next, + .stop = proc_keys_stop, + .show = proc_keys_show, +}; + +static struct file_operations proc_keys_fops = { + .open = proc_keys_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; +#endif + +static int proc_key_users_open(struct inode *inode, struct file *file); +static void *proc_key_users_start(struct seq_file *p, loff_t *_pos); +static void *proc_key_users_next(struct seq_file *p, void *v, loff_t *_pos); +static void proc_key_users_stop(struct seq_file *p, void *v); +static int proc_key_users_show(struct seq_file *m, void *v); + +static struct seq_operations proc_key_users_ops = { + .start = proc_key_users_start, + .next = proc_key_users_next, + .stop = proc_key_users_stop, + .show = proc_key_users_show, +}; + +static struct file_operations proc_key_users_fops = { + .open = proc_key_users_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +/*****************************************************************************/ +/* + * declare the /proc files + */ +static int __init key_proc_init(void) +{ + struct proc_dir_entry *p; + +#ifdef CONFIG_KEYS_DEBUG_PROC_KEYS + p = create_proc_entry("keys", 0, NULL); + if (!p) + panic("Cannot create /proc/keys\n"); + + p->proc_fops = &proc_keys_fops; +#endif + + p = create_proc_entry("key-users", 0, NULL); + if (!p) + panic("Cannot create /proc/key-users\n"); + + p->proc_fops = &proc_key_users_fops; + + return 0; + +} /* end key_proc_init() */ + +__initcall(key_proc_init); + +/*****************************************************************************/ +/* + * implement "/proc/keys" to provides a list of the keys on the system + */ +#ifdef CONFIG_KEYS_DEBUG_PROC_KEYS + +static int proc_keys_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &proc_keys_ops); + +} + +static void *proc_keys_start(struct seq_file *p, loff_t *_pos) +{ + struct rb_node *_p; + loff_t pos = *_pos; + + spin_lock(&key_serial_lock); + + _p = rb_first(&key_serial_tree); + while (pos > 0 && _p) { + pos--; + _p = rb_next(_p); + } + + return _p; + +} + +static void *proc_keys_next(struct seq_file *p, void *v, loff_t *_pos) +{ + (*_pos)++; + return rb_next((struct rb_node *) v); + +} + +static void proc_keys_stop(struct seq_file *p, void *v) +{ + spin_unlock(&key_serial_lock); +} + +static int proc_keys_show(struct seq_file *m, void *v) +{ + struct rb_node *_p = v; + struct key *key = rb_entry(_p, struct key, serial_node); + struct timespec now; + unsigned long timo; + char xbuf[12]; + + now = current_kernel_time(); + + read_lock(&key->lock); + + /* come up with a suitable timeout value */ + if (key->expiry == 0) { + memcpy(xbuf, "perm", 5); + } + else if (now.tv_sec >= key->expiry) { + memcpy(xbuf, "expd", 5); + } + else { + timo = key->expiry - now.tv_sec; + + if (timo < 60) + sprintf(xbuf, "%lus", timo); + else if (timo < 60*60) + sprintf(xbuf, "%lum", timo / 60); + else if (timo < 60*60*24) + sprintf(xbuf, "%luh", timo / (60*60)); + else if (timo < 60*60*24*7) + sprintf(xbuf, "%lud", timo / (60*60*24)); + else + sprintf(xbuf, "%luw", timo / (60*60*24*7)); + } + + seq_printf(m, "%08x %c%c%c%c%c%c %5d %4s %06x %5d %5d %-9.9s ", + key->serial, + key->flags & KEY_FLAG_INSTANTIATED ? 'I' : '-', + key->flags & KEY_FLAG_REVOKED ? 'R' : '-', + key->flags & KEY_FLAG_DEAD ? 'D' : '-', + key->flags & KEY_FLAG_IN_QUOTA ? 'Q' : '-', + key->flags & KEY_FLAG_USER_CONSTRUCT ? 'U' : '-', + key->flags & KEY_FLAG_NEGATIVE ? 'N' : '-', + atomic_read(&key->usage), + xbuf, + key->perm, + key->uid, + key->gid, + key->type->name); + + if (key->type->describe) + key->type->describe(key, m); + seq_putc(m, '\n'); + + read_unlock(&key->lock); + + return 0; + +} + +#endif /* CONFIG_KEYS_DEBUG_PROC_KEYS */ + +/*****************************************************************************/ +/* + * implement "/proc/key-users" to provides a list of the key users + */ +static int proc_key_users_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &proc_key_users_ops); + +} + +static void *proc_key_users_start(struct seq_file *p, loff_t *_pos) +{ + struct rb_node *_p; + loff_t pos = *_pos; + + spin_lock(&key_user_lock); + + _p = rb_first(&key_user_tree); + while (pos > 0 && _p) { + pos--; + _p = rb_next(_p); + } + + return _p; + +} + +static void *proc_key_users_next(struct seq_file *p, void *v, loff_t *_pos) +{ + (*_pos)++; + return rb_next((struct rb_node *) v); + +} + +static void proc_key_users_stop(struct seq_file *p, void *v) +{ + spin_unlock(&key_user_lock); +} + +static int proc_key_users_show(struct seq_file *m, void *v) +{ + struct rb_node *_p = v; + struct key_user *user = rb_entry(_p, struct key_user, node); + + seq_printf(m, "%5u: %5d %d/%d %d/%d %d/%d\n", + user->uid, + atomic_read(&user->usage), + atomic_read(&user->nkeys), + atomic_read(&user->nikeys), + user->qnkeys, + KEYQUOTA_MAX_KEYS, + user->qnbytes, + KEYQUOTA_MAX_BYTES + ); + + return 0; + +} diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c new file mode 100644 index 000000000000..e5bd7b940550 --- /dev/null +++ b/security/keys/process_keys.c @@ -0,0 +1,640 @@ +/* process_keys.c: management of a process's keyrings + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "internal.h" + +/* session keyring create vs join semaphore */ +static DECLARE_MUTEX(key_session_sem); + +/* the root user's tracking struct */ +struct key_user root_key_user = { + .usage = ATOMIC_INIT(3), + .consq = LIST_HEAD_INIT(root_key_user.consq), + .lock = SPIN_LOCK_UNLOCKED, + .nkeys = ATOMIC_INIT(2), + .nikeys = ATOMIC_INIT(2), + .uid = 0, +}; + +/* the root user's UID keyring */ +struct key root_user_keyring = { + .usage = ATOMIC_INIT(1), + .serial = 2, + .type = &key_type_keyring, + .user = &root_key_user, + .lock = RW_LOCK_UNLOCKED, + .sem = __RWSEM_INITIALIZER(root_user_keyring.sem), + .perm = KEY_USR_ALL, + .flags = KEY_FLAG_INSTANTIATED, + .description = "_uid.0", +#ifdef KEY_DEBUGGING + .magic = KEY_DEBUG_MAGIC, +#endif +}; + +/* the root user's default session keyring */ +struct key root_session_keyring = { + .usage = ATOMIC_INIT(1), + .serial = 1, + .type = &key_type_keyring, + .user = &root_key_user, + .lock = RW_LOCK_UNLOCKED, + .sem = __RWSEM_INITIALIZER(root_session_keyring.sem), + .perm = KEY_USR_ALL, + .flags = KEY_FLAG_INSTANTIATED, + .description = "_uid_ses.0", +#ifdef KEY_DEBUGGING + .magic = KEY_DEBUG_MAGIC, +#endif +}; + +/*****************************************************************************/ +/* + * allocate the keyrings to be associated with a UID + */ +int alloc_uid_keyring(struct user_struct *user) +{ + struct key *uid_keyring, *session_keyring; + char buf[20]; + int ret; + + /* concoct a default session keyring */ + sprintf(buf, "_uid_ses.%u", user->uid); + + session_keyring = keyring_alloc(buf, user->uid, (gid_t) -1, 0, NULL); + if (IS_ERR(session_keyring)) { + ret = PTR_ERR(session_keyring); + goto error; + } + + /* and a UID specific keyring, pointed to by the default session + * keyring */ + sprintf(buf, "_uid.%u", user->uid); + + uid_keyring = keyring_alloc(buf, user->uid, (gid_t) -1, 0, + session_keyring); + if (IS_ERR(uid_keyring)) { + key_put(session_keyring); + ret = PTR_ERR(uid_keyring); + goto error; + } + + /* install the keyrings */ + user->uid_keyring = uid_keyring; + user->session_keyring = session_keyring; + ret = 0; + + error: + return ret; + +} /* end alloc_uid_keyring() */ + +/*****************************************************************************/ +/* + * deal with the UID changing + */ +void switch_uid_keyring(struct user_struct *new_user) +{ +#if 0 /* do nothing for now */ + struct key *old; + + /* switch to the new user's session keyring if we were running under + * root's default session keyring */ + if (new_user->uid != 0 && + current->session_keyring == &root_session_keyring + ) { + atomic_inc(&new_user->session_keyring->usage); + + task_lock(current); + old = current->session_keyring; + current->session_keyring = new_user->session_keyring; + task_unlock(current); + + key_put(old); + } +#endif + +} /* end switch_uid_keyring() */ + +/*****************************************************************************/ +/* + * install a fresh thread keyring, discarding the old one + */ +int install_thread_keyring(struct task_struct *tsk) +{ + struct key *keyring, *old; + char buf[20]; + int ret; + + sprintf(buf, "_tid.%u", tsk->pid); + + keyring = keyring_alloc(buf, tsk->uid, tsk->gid, 1, NULL); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + + task_lock(tsk); + old = tsk->thread_keyring; + tsk->thread_keyring = keyring; + task_unlock(tsk); + + ret = 0; + + key_put(old); + error: + return ret; + +} /* end install_thread_keyring() */ + +/*****************************************************************************/ +/* + * install a fresh process keyring, discarding the old one + */ +static int install_process_keyring(struct task_struct *tsk) +{ + struct key *keyring, *old; + char buf[20]; + int ret; + + sprintf(buf, "_pid.%u", tsk->tgid); + + keyring = keyring_alloc(buf, tsk->uid, tsk->gid, 1, NULL); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + + task_lock(tsk); + old = tsk->process_keyring; + tsk->process_keyring = keyring; + task_unlock(tsk); + + ret = 0; + + key_put(old); + error: + return ret; + +} /* end install_process_keyring() */ + +/*****************************************************************************/ +/* + * install a session keyring, discarding the old one + * - if a keyring is not supplied, an empty one is invented + */ +static int install_session_keyring(struct task_struct *tsk, + struct key *keyring) +{ + struct key *old; + char buf[20]; + int ret; + + /* create an empty session keyring */ + if (!keyring) { + sprintf(buf, "_ses.%u", tsk->tgid); + + keyring = keyring_alloc(buf, tsk->uid, tsk->gid, 1, NULL); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + } + else { + atomic_inc(&keyring->usage); + } + + /* install the keyring */ + task_lock(tsk); + old = tsk->session_keyring; + tsk->session_keyring = keyring; + task_unlock(tsk); + + ret = 0; + + key_put(old); + error: + return ret; + +} /* end install_session_keyring() */ + +/*****************************************************************************/ +/* + * copy the keys for fork + */ +int copy_keys(unsigned long clone_flags, struct task_struct *tsk) +{ + int ret = 0; + + key_check(tsk->session_keyring); + key_check(tsk->process_keyring); + key_check(tsk->thread_keyring); + + if (tsk->session_keyring) + atomic_inc(&tsk->session_keyring->usage); + + if (tsk->process_keyring) { + if (clone_flags & CLONE_THREAD) { + atomic_inc(&tsk->process_keyring->usage); + } + else { + tsk->process_keyring = NULL; + ret = install_process_keyring(tsk); + } + } + + tsk->thread_keyring = NULL; + return ret; + +} /* end copy_keys() */ + +/*****************************************************************************/ +/* + * dispose of keys upon exit + */ +void exit_keys(struct task_struct *tsk) +{ + key_put(tsk->session_keyring); + key_put(tsk->process_keyring); + key_put(tsk->thread_keyring); + +} /* end exit_keys() */ + +/*****************************************************************************/ +/* + * deal with execve() + */ +int exec_keys(struct task_struct *tsk) +{ + struct key *old; + + /* newly exec'd tasks don't get a thread keyring */ + task_lock(tsk); + old = tsk->thread_keyring; + tsk->thread_keyring = NULL; + task_unlock(tsk); + + key_put(old); + + /* newly exec'd tasks get a fresh process keyring */ + return install_process_keyring(tsk); + +} /* end exec_keys() */ + +/*****************************************************************************/ +/* + * deal with SUID programs + * - we might want to make this invent a new session keyring + */ +int suid_keys(struct task_struct *tsk) +{ + return 0; + +} /* end suid_keys() */ + +/*****************************************************************************/ +/* + * the filesystem user ID changed + */ +void key_fsuid_changed(struct task_struct *tsk) +{ + /* update the ownership of the process keyring */ + if (tsk->process_keyring) { + down_write(&tsk->process_keyring->sem); + write_lock(&tsk->process_keyring->lock); + tsk->process_keyring->uid = tsk->fsuid; + write_unlock(&tsk->process_keyring->lock); + up_write(&tsk->process_keyring->sem); + } + + /* update the ownership of the thread keyring */ + if (tsk->thread_keyring) { + down_write(&tsk->thread_keyring->sem); + write_lock(&tsk->thread_keyring->lock); + tsk->thread_keyring->uid = tsk->fsuid; + write_unlock(&tsk->thread_keyring->lock); + up_write(&tsk->thread_keyring->sem); + } + +} /* end key_fsuid_changed() */ + +/*****************************************************************************/ +/* + * the filesystem group ID changed + */ +void key_fsgid_changed(struct task_struct *tsk) +{ + /* update the ownership of the process keyring */ + if (tsk->process_keyring) { + down_write(&tsk->process_keyring->sem); + write_lock(&tsk->process_keyring->lock); + tsk->process_keyring->gid = tsk->fsgid; + write_unlock(&tsk->process_keyring->lock); + up_write(&tsk->process_keyring->sem); + } + + /* update the ownership of the thread keyring */ + if (tsk->thread_keyring) { + down_write(&tsk->thread_keyring->sem); + write_lock(&tsk->thread_keyring->lock); + tsk->thread_keyring->gid = tsk->fsgid; + write_unlock(&tsk->thread_keyring->lock); + up_write(&tsk->thread_keyring->sem); + } + +} /* end key_fsgid_changed() */ + +/*****************************************************************************/ +/* + * search the process keyrings for the first matching key + * - we use the supplied match function to see if the description (or other + * feature of interest) matches + * - we return -EAGAIN if we didn't find any matching key + * - we return -ENOKEY if we found only negative matching keys + */ +struct key *search_process_keyrings_aux(struct key_type *type, + const void *description, + key_match_func_t match) +{ + struct task_struct *tsk = current; + struct key *key, *ret, *err, *session; + + /* we want to return -EAGAIN or -ENOKEY if any of the keyrings were + * searchable, but we failed to find a key or we found a negative key; + * otherwise we want to return a sample error (probably -EACCES) if + * none of the keyrings were searchable + * + * in terms of priority: success > -ENOKEY > -EAGAIN > other error + */ + key = NULL; + ret = NULL; + err = ERR_PTR(-EAGAIN); + + /* search the thread keyring first */ + if (tsk->thread_keyring) { + key = keyring_search_aux(tsk->thread_keyring, type, + description, match); + if (!IS_ERR(key)) + goto found; + + switch (PTR_ERR(key)) { + case -EAGAIN: /* no key */ + if (ret) + break; + case -ENOKEY: /* negative key */ + ret = key; + break; + default: + err = key; + break; + } + } + + /* search the process keyring second */ + if (tsk->process_keyring) { + key = keyring_search_aux(tsk->process_keyring, type, + description, match); + if (!IS_ERR(key)) + goto found; + + switch (PTR_ERR(key)) { + case -EAGAIN: /* no key */ + if (ret) + break; + case -ENOKEY: /* negative key */ + ret = key; + break; + default: + err = key; + break; + } + } + + /* search the session keyring last */ + session = tsk->session_keyring; + if (!session) + session = tsk->user->session_keyring; + + key = keyring_search_aux(session, type, + description, match); + if (!IS_ERR(key)) + goto found; + + switch (PTR_ERR(key)) { + case -EAGAIN: /* no key */ + if (ret) + break; + case -ENOKEY: /* negative key */ + ret = key; + break; + default: + err = key; + break; + } + + /* no key - decide on the error we're going to go for */ + key = ret ? ret : err; + + found: + return key; + +} /* end search_process_keyrings_aux() */ + +/*****************************************************************************/ +/* + * search the process keyrings for the first matching key + * - we return -EAGAIN if we didn't find any matching key + * - we return -ENOKEY if we found only negative matching keys + */ +struct key *search_process_keyrings(struct key_type *type, + const char *description) +{ + return search_process_keyrings_aux(type, description, type->match); + +} /* end search_process_keyrings() */ + +/*****************************************************************************/ +/* + * lookup a key given a key ID from userspace with a given permissions mask + * - don't create special keyrings unless so requested + * - partially constructed keys aren't found unless requested + */ +struct key *lookup_user_key(key_serial_t id, int create, int partial, + key_perm_t perm) +{ + struct task_struct *tsk = current; + struct key *key; + int ret; + + key = ERR_PTR(-ENOKEY); + + switch (id) { + case KEY_SPEC_THREAD_KEYRING: + if (!tsk->thread_keyring) { + if (!create) + goto error; + + ret = install_thread_keyring(tsk); + if (ret < 0) { + key = ERR_PTR(ret); + goto error; + } + } + + key = tsk->thread_keyring; + atomic_inc(&key->usage); + break; + + case KEY_SPEC_PROCESS_KEYRING: + if (!tsk->process_keyring) { + if (!create) + goto error; + + ret = install_process_keyring(tsk); + if (ret < 0) { + key = ERR_PTR(ret); + goto error; + } + } + + key = tsk->process_keyring; + atomic_inc(&key->usage); + break; + + case KEY_SPEC_SESSION_KEYRING: + if (!tsk->session_keyring) { + /* always install a session keyring upon access if one + * doesn't exist yet */ + ret = install_session_keyring( + tsk, tsk->user->session_keyring); + if (ret < 0) + goto error; + } + + key = tsk->session_keyring; + atomic_inc(&key->usage); + break; + + case KEY_SPEC_USER_KEYRING: + key = tsk->user->uid_keyring; + atomic_inc(&key->usage); + break; + + case KEY_SPEC_USER_SESSION_KEYRING: + key = tsk->user->session_keyring; + atomic_inc(&key->usage); + break; + + case KEY_SPEC_GROUP_KEYRING: + /* group keyrings are not yet supported */ + key = ERR_PTR(-EINVAL); + goto error; + + default: + key = ERR_PTR(-EINVAL); + if (id < 1) + goto error; + + key = key_lookup(id); + if (IS_ERR(key)) + goto error; + break; + } + + /* check the status and permissions */ + if (perm) { + ret = key_validate(key); + if (ret < 0) + goto invalid_key; + } + + ret = -EIO; + if (!partial && !(key->flags & KEY_FLAG_INSTANTIATED)) + goto invalid_key; + + ret = -EACCES; + if (!key_permission(key, perm)) + goto invalid_key; + + error: + return key; + + invalid_key: + key_put(key); + key = ERR_PTR(ret); + goto error; + +} /* end lookup_user_key() */ + +/*****************************************************************************/ +/* + * join the named keyring as the session keyring if possible, or attempt to + * create a new one of that name if not + * - if the name is NULL, an empty anonymous keyring is installed instead + * - named session keyring joining is done with a semaphore held + */ +long join_session_keyring(const char *name) +{ + struct task_struct *tsk = current; + struct key *keyring; + long ret; + + /* if no name is provided, install an anonymous keyring */ + if (!name) { + ret = install_session_keyring(tsk, NULL); + if (ret < 0) + goto error; + + ret = tsk->session_keyring->serial; + goto error; + } + + /* allow the user to join or create a named keyring */ + down(&key_session_sem); + + /* look for an existing keyring of this name */ + keyring = find_keyring_by_name(name, 0); + if (PTR_ERR(keyring) == -ENOKEY) { + /* not found - try and create a new one */ + keyring = keyring_alloc(name, tsk->uid, tsk->gid, 0, NULL); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + } + else if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error2; + } + + /* we've got a keyring - now to install it */ + ret = install_session_keyring(tsk, keyring); + if (ret < 0) + goto error2; + + key_put(keyring); + + ret = tsk->session_keyring->serial; + + error2: + up(&key_session_sem); + error: + return ret; + +} /* end join_session_keyring() */ diff --git a/security/keys/request_key.c b/security/keys/request_key.c new file mode 100644 index 000000000000..fd6ba06f2503 --- /dev/null +++ b/security/keys/request_key.c @@ -0,0 +1,337 @@ +/* request_key.c: request a key from userspace + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include "internal.h" + +struct key_construction { + struct list_head link; /* link in construction queue */ + struct key *key; /* key being constructed */ +}; + +/* when waiting for someone else's keys, you get added to this */ +DECLARE_WAIT_QUEUE_HEAD(request_key_conswq); + +/*****************************************************************************/ +/* + * request userspace finish the construction of a key + * - execute "/sbin/request-key " + * - if callout_info is an empty string, it'll be rendered as a "-" instead + */ +static int call_request_key(struct key *key, + const char *op, + const char *callout_info) +{ + struct task_struct *tsk = current; + char *argv[10], *envp[3], uid_str[12], gid_str[12]; + char key_str[12], keyring_str[3][12]; + int i; + + /* record the UID and GID */ + sprintf(uid_str, "%d", current->fsuid); + sprintf(gid_str, "%d", current->fsgid); + + /* we say which key is under construction */ + sprintf(key_str, "%d", key->serial); + + /* we specify the process's default keyrings */ + task_lock(current); + sprintf(keyring_str[0], "%d", + tsk->thread_keyring ? tsk->thread_keyring->serial : 0); + sprintf(keyring_str[1], "%d", + tsk->process_keyring ? tsk->process_keyring->serial : 0); + sprintf(keyring_str[2], "%d", + (tsk->session_keyring ? + tsk->session_keyring->serial : + tsk->user->session_keyring->serial)); + task_unlock(tsk); + + /* set up a minimal environment */ + i = 0; + envp[i++] = "HOME=/"; + envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin"; + envp[i] = NULL; + + /* set up the argument list */ + i = 0; + argv[i++] = "/sbin/request-key"; + argv[i++] = (char *) op; + argv[i++] = key_str; + argv[i++] = uid_str; + argv[i++] = gid_str; + argv[i++] = keyring_str[0]; + argv[i++] = keyring_str[1]; + argv[i++] = keyring_str[2]; + argv[i++] = callout_info[0] ? (char *) callout_info : "-"; + argv[i] = NULL; + + /* do it */ + return call_usermodehelper(argv[0], argv, envp, 1); + +} /* end call_request_key() */ + +/*****************************************************************************/ +/* + * call out to userspace for the key + * - called with the construction sem held, but the sem is dropped here + * - we ignore program failure and go on key status instead + */ +static struct key *__request_key_construction(struct key_type *type, + const char *description, + const char *callout_info) +{ + struct key_construction cons; + struct timespec now; + struct key *key; + int ret, negative; + + /* create a key and add it to the queue */ + key = key_alloc(type, description, + current->fsuid, current->fsgid, KEY_USR_ALL, 0); + if (IS_ERR(key)) + goto alloc_failed; + + write_lock(&key->lock); + key->flags |= KEY_FLAG_USER_CONSTRUCT; + write_unlock(&key->lock); + + cons.key = key; + list_add_tail(&cons.link, &key->user->consq); + + /* we drop the construction sem here on behalf of the caller */ + up_write(&key_construction_sem); + + /* make the call */ + ret = call_request_key(key, "create", callout_info); + if (ret < 0) + goto request_failed; + + /* if the key wasn't instantiated, then we want to give an error */ + ret = -ENOKEY; + if (!(key->flags & KEY_FLAG_INSTANTIATED)) + goto request_failed; + + down_write(&key_construction_sem); + list_del(&cons.link); + up_write(&key_construction_sem); + + /* also give an error if the key was negatively instantiated */ + check_not_negative: + if (key->flags & KEY_FLAG_NEGATIVE) { + key_put(key); + key = ERR_PTR(-ENOKEY); + } + + out: + return key; + + request_failed: + /* it wasn't instantiated + * - remove from construction queue + * - mark the key as dead + */ + negative = 0; + down_write(&key_construction_sem); + + list_del(&cons.link); + + write_lock(&key->lock); + key->flags &= ~KEY_FLAG_USER_CONSTRUCT; + + /* check it didn't get instantiated between the check and the down */ + if (!(key->flags & KEY_FLAG_INSTANTIATED)) { + key->flags |= KEY_FLAG_INSTANTIATED | KEY_FLAG_NEGATIVE; + negative = 1; + } + + write_unlock(&key->lock); + up_write(&key_construction_sem); + + if (!negative) + goto check_not_negative; /* surprisingly, the key got + * instantiated */ + + /* set the timeout and store in the session keyring if we can */ + now = current_kernel_time(); + key->expiry = now.tv_sec + key_negative_timeout; + + if (current->session_keyring) + key_link(current->session_keyring, key); + key_put(key); + + /* notify anyone who was waiting */ + wake_up_all(&request_key_conswq); + + key = ERR_PTR(ret); + goto out; + + alloc_failed: + up_write(&key_construction_sem); + goto out; + +} /* end __request_key_construction() */ + +/*****************************************************************************/ +/* + * call out to userspace to request the key + * - we check the construction queue first to see if an appropriate key is + * already being constructed by userspace + */ +static struct key *request_key_construction(struct key_type *type, + const char *description, + struct key_user *user, + const char *callout_info) +{ + struct key_construction *pcons; + struct key *key, *ckey; + + DECLARE_WAITQUEUE(myself, current); + + /* see if there's such a key under construction already */ + down_write(&key_construction_sem); + + list_for_each_entry(pcons, &user->consq, link) { + ckey = pcons->key; + + if (ckey->type != type) + continue; + + if (type->match(ckey, description)) + goto found_key_under_construction; + } + + /* see about getting userspace to construct the key */ + key = __request_key_construction(type, description, callout_info); + error: + return key; + + /* someone else has the same key under construction + * - we want to keep an eye on their key + */ + found_key_under_construction: + atomic_inc(&ckey->usage); + up_write(&key_construction_sem); + + /* wait for the key to be completed one way or another */ + add_wait_queue(&request_key_conswq, &myself); + + for (;;) { + set_current_state(TASK_UNINTERRUPTIBLE); + if (!(ckey->flags & KEY_FLAG_USER_CONSTRUCT)) + break; + schedule(); + } + + set_current_state(TASK_RUNNING); + remove_wait_queue(&request_key_conswq, &myself); + + /* we'll need to search this process's keyrings to see if the key is + * now there since we can't automatically assume it's also available + * there */ + key_put(ckey); + ckey = NULL; + + key = NULL; /* request a retry */ + goto error; + +} /* end request_key_construction() */ + +/*****************************************************************************/ +/* + * request a key + * - search the process's keyrings + * - check the list of keys being created or updated + * - call out to userspace for a key if requested (supplementary info can be + * passed) + */ +struct key *request_key(struct key_type *type, + const char *description, + const char *callout_info) +{ + struct key_user *user; + struct key *key; + + /* search all the process keyrings for a key */ + key = search_process_keyrings_aux(type, description, type->match); + + if (PTR_ERR(key) == -EAGAIN) { + /* the search failed, but the keyrings were searchable, so we + * should consult userspace if we can */ + key = ERR_PTR(-ENOKEY); + if (!callout_info) + goto error; + + /* - get hold of the user's construction queue */ + user = key_user_lookup(current->fsuid); + if (IS_ERR(user)) { + key = ERR_PTR(PTR_ERR(user)); + goto error; + } + + for (;;) { + /* ask userspace (returns NULL if it waited on a key + * being constructed) */ + key = request_key_construction(type, description, + user, callout_info); + if (key) + break; + + /* someone else made the key we want, so we need to + * search again as it might now be available to us */ + key = search_process_keyrings_aux(type, description, + type->match); + if (PTR_ERR(key) != -EAGAIN) + break; + } + + key_user_put(user); + } + + error: + return key; + +} /* end request_key() */ + +EXPORT_SYMBOL(request_key); + +/*****************************************************************************/ +/* + * validate a key + */ +int key_validate(struct key *key) +{ + struct timespec now; + int ret = 0; + + if (key) { + /* check it's still accessible */ + ret = -EKEYREVOKED; + if (key->flags & (KEY_FLAG_REVOKED | KEY_FLAG_DEAD)) + goto error; + + /* check it hasn't expired */ + ret = 0; + if (key->expiry) { + now = current_kernel_time(); + if (now.tv_sec >= key->expiry) + ret = -EKEYEXPIRED; + } + } + + error: + return ret; + +} /* end key_validate() */ + +EXPORT_SYMBOL(key_validate); diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c new file mode 100644 index 000000000000..8d65b3a28129 --- /dev/null +++ b/security/keys/user_defined.c @@ -0,0 +1,191 @@ +/* user_defined.c: user defined key type + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "internal.h" + +static int user_instantiate(struct key *key, const void *data, size_t datalen); +static int user_duplicate(struct key *key, const struct key *source); +static int user_update(struct key *key, const void *data, size_t datalen); +static int user_match(const struct key *key, const void *criterion); +static void user_destroy(struct key *key); +static void user_describe(const struct key *user, struct seq_file *m); +static long user_read(const struct key *key, + char __user *buffer, size_t buflen); + +/* + * user defined keys take an arbitrary string as the description and an + * arbitrary blob of data as the payload + */ +struct key_type key_type_user = { + .name = "user", + .instantiate = user_instantiate, + .duplicate = user_duplicate, + .update = user_update, + .match = user_match, + .destroy = user_destroy, + .describe = user_describe, + .read = user_read, +}; + +/*****************************************************************************/ +/* + * instantiate a user defined key + */ +static int user_instantiate(struct key *key, const void *data, size_t datalen) +{ + int ret; + + ret = -EINVAL; + if (datalen <= 0 || datalen > 32767 || !data) + goto error; + + ret = key_payload_reserve(key, datalen); + if (ret < 0) + goto error; + + /* attach the data */ + ret = -ENOMEM; + key->payload.data = kmalloc(datalen, GFP_KERNEL); + if (!key->payload.data) + goto error; + + memcpy(key->payload.data, data, datalen); + ret = 0; + + error: + return ret; + +} /* end user_instantiate() */ + +/*****************************************************************************/ +/* + * duplicate a user defined key + */ +static int user_duplicate(struct key *key, const struct key *source) +{ + int ret; + + /* just copy the payload */ + ret = -ENOMEM; + key->payload.data = kmalloc(source->datalen, GFP_KERNEL); + + if (key->payload.data) { + key->datalen = source->datalen; + memcpy(key->payload.data, source->payload.data, source->datalen); + ret = 0; + } + + return ret; + +} /* end user_duplicate() */ + +/*****************************************************************************/ +/* + * update a user defined key + */ +static int user_update(struct key *key, const void *data, size_t datalen) +{ + void *new, *zap; + int ret; + + ret = -EINVAL; + if (datalen <= 0 || datalen > 32767 || !data) + goto error; + + /* copy the data */ + ret = -ENOMEM; + new = kmalloc(datalen, GFP_KERNEL); + if (!new) + goto error; + + memcpy(new, data, datalen); + + /* check the quota and attach the new data */ + zap = new; + write_lock(&key->lock); + + ret = key_payload_reserve(key, datalen); + + if (ret == 0) { + /* attach the new data, displacing the old */ + zap = key->payload.data; + key->payload.data = new; + key->expiry = 0; + } + + write_unlock(&key->lock); + kfree(zap); + + error: + return ret; + +} /* end user_update() */ + +/*****************************************************************************/ +/* + * match users on their name + */ +static int user_match(const struct key *key, const void *description) +{ + return strcmp(key->description, description) == 0; + +} /* end user_match() */ + +/*****************************************************************************/ +/* + * dispose of the data dangling from the corpse of a user + */ +static void user_destroy(struct key *key) +{ + kfree(key->payload.data); + +} /* end user_destroy() */ + +/*****************************************************************************/ +/* + * describe the user + */ +static void user_describe(const struct key *key, struct seq_file *m) +{ + seq_puts(m, key->description); + + seq_printf(m, ": %u", key->datalen); + +} /* end user_describe() */ + +/*****************************************************************************/ +/* + * read the key data + */ +static long user_read(const struct key *key, + char __user *buffer, size_t buflen) +{ + long ret = key->datalen; + + /* we can return the data as is */ + if (buffer && buflen > 0) { + if (buflen > key->datalen) + buflen = key->datalen; + + if (copy_to_user(buffer, key->payload.data, buflen) != 0) + ret = -EFAULT; + } + + return ret; + +} /* end user_read() */ -- cgit v1.2.3 From e10392112d315c45f054c22c862e3a7ae27d17d4 Mon Sep 17 00:00:00 2001 From: Paulo Marques Date: Mon, 18 Oct 2004 17:59:03 -0700 Subject: [PATCH] kallsyms data size reduction / lookup speedup This patch is an improvement over my first kallsyms speedup patch posted about 2 weeks ago. It changes scripts/kallsyms as to produce a different format for kallsyms_names and extra data to speedup lookups. The compression algorithm is quite simple: it uses all the char codes not actually used in symbols to build a lookup table that translates these codes into small strings. For instance, in my test runs the code 0xFE was being translated into "acpi_" giving a 4 byte save on every translation. The advantage of this algorithm is that to translate a symbol we only require information that is stored on that symbol position, and never need to go back on the compressed stream to get information from other symbols. To give an idea about the benefits of this algorithm here are some benchmark results on a P4 2.8GHz with a symbol table with 10000 entries: kallsyms_lookup average time: vanilla 1346.0 us speedup 14.4 us with this patch 0.5 us total data produced by scripts/kallsyms: uncompressed 169 Kb vanilla 134 Kb with this patch 91 Kb (speedup was my latest patch, that only changed the way kallsyms_lookup worked and not the data format) I removed a cond_resched() from the proc/kallsyms handling code path, because using stem compression, if the current position went backwards, the hole stream would be uncompressed up to the current position. It seemed that by removing this loop it would be safe to remove the conditional reschedule altogether. There is just one catch with this patch: the time it takes to compile the kernel goes up just a bit (about 0.8s on a P4 2.8GHz with defconfig). If this delay is not acceptable I can change the compression algorithm so that it can use the previous table (calculating a new table is what consumes most of the time, and not doing the actual compression) and check to see if it obtains a similar compression ratio. If it does, then this is a sign that the symbol patterns haven't changed that much and this table is still good to use. This would not only cut the time down to half on any compilation (because of the 2 pass symbol build method), but in frequent cases where a developer is compiling a single file and linking everything over and over again, the table optimization process would never run. I'm CC'ing Brent Casavant on this email, because last june he sent a patch trying a different approach that used a 32 entry symbol cache, because there was a problem with the time "top" took to read "proc//wchan". I was hopping he would be willing to test this patch and comment on the results. Signed-off-by: Paulo Marques Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- kernel/kallsyms.c | 194 +++++++++++++------ scripts/kallsyms.c | 553 +++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 648 insertions(+), 99 deletions(-) diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c index 74ba3cb21809..8f3c6c1d1ce7 100644 --- a/kernel/kallsyms.c +++ b/kernel/kallsyms.c @@ -4,7 +4,12 @@ * Rewritten and vastly simplified by Rusty Russell for in-kernel * module loader: * Copyright 2002 Rusty Russell IBM Corporation - * Stem compression by Andi Kleen. + * + * ChangeLog: + * + * (25/Aug/2004) Paulo Marques + * Changed the compression method from stem compression to "table lookup" + * compression (see scripts/kallsyms.c for a more complete description) */ #include #include @@ -17,7 +22,12 @@ /* These will be re-linked against their real values during the second link stage */ extern unsigned long kallsyms_addresses[] __attribute__((weak)); extern unsigned long kallsyms_num_syms __attribute__((weak)); -extern char kallsyms_names[] __attribute__((weak)); +extern u8 kallsyms_names[] __attribute__((weak)); + +extern u8 kallsyms_token_table[] __attribute__((weak)); +extern u16 kallsyms_token_index[] __attribute__((weak)); + +extern unsigned long kallsyms_markers[] __attribute__((weak)); /* Defined by the linker script. */ extern char _stext[], _etext[], _sinittext[], _einittext[]; @@ -37,21 +47,88 @@ static inline int is_kernel_text(unsigned long addr) return 0; } +/* expand a compressed symbol data into the resulting uncompressed string, + given the offset to where the symbol is in the compressed stream */ +static unsigned int kallsyms_expand_symbol(unsigned int off, char *result) +{ + int len, skipped_first = 0; + u8 *tptr, *data; + + /* get the compressed symbol length from the first symbol byte */ + data = &kallsyms_names[off]; + len = *data; + data++; + + /* update the offset to return the offset for the next symbol on + * the compressed stream */ + off += len + 1; + + /* for every byte on the compressed symbol data, copy the table + entry for that byte */ + while(len) { + tptr = &kallsyms_token_table[ kallsyms_token_index[*data] ]; + data++; + len--; + + while (*tptr) { + if(skipped_first) { + *result = *tptr; + result++; + } else + skipped_first = 1; + tptr++; + } + } + + *result = '\0'; + + /* return to offset to the next symbol */ + return off; +} + +/* get symbol type information. This is encoded as a single char at the + * begining of the symbol name */ +static char kallsyms_get_symbol_type(unsigned int off) +{ + /* get just the first code, look it up in the token table, and return the + * first char from this token */ + return kallsyms_token_table[ kallsyms_token_index[ kallsyms_names[off+1] ] ]; +} + + +/* find the offset on the compressed stream given and index in the + * kallsyms array */ +static unsigned int get_symbol_offset(unsigned long pos) +{ + u8 *name; + int i; + + /* use the closest marker we have. We have markers every 256 positions, + * so that should be close enough */ + name = &kallsyms_names[ kallsyms_markers[pos>>8] ]; + + /* sequentially scan all the symbols up to the point we're searching for. + * Every symbol is stored in a [][ bytes of data] format, so we + * just need to add the len to the current pointer for every symbol we + * wish to skip */ + for(i = 0; i < (pos&0xFF); i++) + name = name + (*name) + 1; + + return name - kallsyms_names; +} + /* Lookup the address for this symbol. Returns 0 if not found. */ unsigned long kallsyms_lookup_name(const char *name) { char namebuf[KSYM_NAME_LEN+1]; unsigned long i; - char *knames; + unsigned int off; - for (i = 0, knames = kallsyms_names; i < kallsyms_num_syms; i++) { - unsigned prefix = *knames++; + for (i = 0, off = 0; i < kallsyms_num_syms; i++) { + off = kallsyms_expand_symbol(off, namebuf); - strlcpy(namebuf + prefix, knames, KSYM_NAME_LEN - prefix); if (strcmp(namebuf, name) == 0) return kallsyms_addresses[i]; - - knames += strlen(knames) + 1; } return module_kallsyms_lookup_name(name); } @@ -62,7 +139,7 @@ const char *kallsyms_lookup(unsigned long addr, unsigned long *offset, char **modname, char *namebuf) { - unsigned long i, best = 0; + unsigned long i, low, high, mid; /* This kernel should never had been booted. */ BUG_ON(!kallsyms_addresses); @@ -71,40 +148,45 @@ const char *kallsyms_lookup(unsigned long addr, namebuf[0] = 0; if (is_kernel_text(addr) || is_kernel_inittext(addr)) { - unsigned long symbol_end; - char *name = kallsyms_names; - - /* They're sorted, we could be clever here, but who cares? */ - for (i = 0; i < kallsyms_num_syms; i++) { - if (kallsyms_addresses[i] > kallsyms_addresses[best] && - kallsyms_addresses[i] <= addr) - best = i; - } + unsigned long symbol_end=0; - /* Grab name */ - for (i = 0; i <= best; i++) { - unsigned prefix = *name++; - strncpy(namebuf + prefix, name, KSYM_NAME_LEN - prefix); - name += strlen(name) + 1; + /* do a binary search on the sorted kallsyms_addresses array */ + low = 0; + high = kallsyms_num_syms; + + while (high-low > 1) { + mid = (low + high) / 2; + if (kallsyms_addresses[mid] <= addr) low = mid; + else high = mid; } - /* At worst, symbol ends at end of section. */ - if (is_kernel_inittext(addr)) - symbol_end = (unsigned long)_einittext; - else - symbol_end = (unsigned long)_etext; + /* search for the first aliased symbol. Aliased symbols are + symbols with the same address */ + while (low && kallsyms_addresses[low - 1] == kallsyms_addresses[low]) + --low; + + /* Grab name */ + kallsyms_expand_symbol(get_symbol_offset(low), namebuf); /* Search for next non-aliased symbol */ - for (i = best+1; i < kallsyms_num_syms; i++) { - if (kallsyms_addresses[i] > kallsyms_addresses[best]) { + for (i = low + 1; i < kallsyms_num_syms; i++) { + if (kallsyms_addresses[i] > kallsyms_addresses[low]) { symbol_end = kallsyms_addresses[i]; break; } } - *symbolsize = symbol_end - kallsyms_addresses[best]; + /* if we found no next symbol, we use the end of the section */ + if (!symbol_end) { + if (is_kernel_inittext(addr)) + symbol_end = (unsigned long)_einittext; + else + symbol_end = (unsigned long)_etext; + } + + *symbolsize = symbol_end - kallsyms_addresses[low]; *modname = NULL; - *offset = addr - kallsyms_addresses[best]; + *offset = addr - kallsyms_addresses[low]; return namebuf; } @@ -135,7 +217,7 @@ void __print_symbol(const char *fmt, unsigned long address) printk(fmt, buffer); } -/* To avoid O(n^2) iteration, we carry prefix along. */ +/* To avoid using get_symbol_offset for every symbol, we carry prefix along. */ struct kallsym_iter { loff_t pos; @@ -168,31 +250,23 @@ static int get_ksymbol_mod(struct kallsym_iter *iter) /* Returns space to next name. */ static unsigned long get_ksymbol_core(struct kallsym_iter *iter) { - unsigned stemlen, off = iter->nameoff; - - /* First char of each symbol name indicates prefix length - shared with previous name (stem compression). */ - stemlen = kallsyms_names[off++]; + unsigned off = iter->nameoff; - strlcpy(iter->name+stemlen, kallsyms_names + off, - KSYM_NAME_LEN+1-stemlen); - off += strlen(kallsyms_names + off) + 1; iter->owner = NULL; iter->value = kallsyms_addresses[iter->pos]; - if (is_kernel_text(iter->value) || is_kernel_inittext(iter->value)) - iter->type = 't'; - else - iter->type = 'd'; - upcase_if_global(iter); + iter->type = kallsyms_get_symbol_type(off); + + off = kallsyms_expand_symbol(off, iter->name); + return off - iter->nameoff; } -static void reset_iter(struct kallsym_iter *iter) +static void reset_iter(struct kallsym_iter *iter, loff_t new_pos) { iter->name[0] = '\0'; - iter->nameoff = 0; - iter->pos = 0; + iter->nameoff = get_symbol_offset(new_pos); + iter->pos = new_pos; } /* Returns false if pos at or past end of file. */ @@ -204,16 +278,13 @@ static int update_iter(struct kallsym_iter *iter, loff_t pos) return get_ksymbol_mod(iter); } - /* If we're past the desired position, reset to start. */ - if (pos < iter->pos) - reset_iter(iter); - - /* We need to iterate through the previous symbols: can be slow */ - for (; iter->pos != pos; iter->pos++) { - iter->nameoff += get_ksymbol_core(iter); - cond_resched(); - } - get_ksymbol_core(iter); + /* If we're not on the desired position, reset to new position. */ + if (pos != iter->pos) + reset_iter(iter, pos); + + iter->nameoff += get_ksymbol_core(iter); + iter->pos++; + return 1; } @@ -267,14 +338,15 @@ struct seq_operations kallsyms_op = { static int kallsyms_open(struct inode *inode, struct file *file) { /* We keep iterator in m->private, since normal case is to - * s_start from where we left off, so we avoid O(N^2). */ + * s_start from where we left off, so we avoid doing + * using get_symbol_offset for every symbol */ struct kallsym_iter *iter; int ret; iter = kmalloc(sizeof(*iter), GFP_KERNEL); if (!iter) return -ENOMEM; - reset_iter(iter); + reset_iter(iter, 0); ret = seq_open(file, &kallsyms_op); if (ret == 0) diff --git a/scripts/kallsyms.c b/scripts/kallsyms.c index e21a5d1a255a..2e0e07afb84c 100644 --- a/scripts/kallsyms.c +++ b/scripts/kallsyms.c @@ -6,6 +6,22 @@ * of the GNU General Public License, incorporated herein by reference. * * Usage: nm -n vmlinux | scripts/kallsyms [--all-symbols] > symbols.S + * + * ChangeLog: + * + * (25/Aug/2004) Paulo Marques + * Changed the compression method from stem compression to "table lookup" + * compression + * + * Table compression uses all the unused char codes on the symbols and + * maps these to the most used substrings (tokens). For instance, it might + * map char code 0xF7 to represent "write_" and then in every symbol where + * "write_" appears it can be replaced by 0xF7, saving 5 bytes. + * The used codes themselves are also placed in the table so that the + * decompresion can work without "special cases". + * Applied to kernel symbols, this usually produces a compression ratio + * of about 50%. + * */ #include @@ -13,10 +29,39 @@ #include #include +/* maximum token length used. It doesn't pay to increase it a lot, because + * very long substrings probably don't repeat themselves too often. */ +#define MAX_TOK_SIZE 11 +#define KSYM_NAME_LEN 127 + +/* we use only a subset of the complete symbol table to gather the token count, + * to speed up compression, at the expense of a little compression ratio */ +#define WORKING_SET 1024 + +/* first find the best token only on the list of tokens that would profit more + * than GOOD_BAD_THRESHOLD. Only if this list is empty go to the "bad" list. + * Increasing this value will put less tokens on the "good" list, so the search + * is faster. However, if the good list runs out of tokens, we must painfully + * search the bad list. */ +#define GOOD_BAD_THRESHOLD 10 + +/* token hash parameters */ +#define HASH_BITS 18 +#define HASH_TABLE_SIZE (1 << HASH_BITS) +#define HASH_MASK (HASH_TABLE_SIZE - 1) +#define HASH_BASE_OFFSET 2166136261U +#define HASH_FOLD(a) ((a)&(HASH_MASK)) + +/* flags to mark symbols */ +#define SYM_FLAG_VALID 1 +#define SYM_FLAG_SAMPLED 2 + struct sym_entry { unsigned long long addr; char type; - char *sym; + unsigned char flags; + unsigned char len; + unsigned char *sym; }; @@ -25,6 +70,26 @@ static int size, cnt; static unsigned long long _stext, _etext, _sinittext, _einittext; static int all_symbols = 0; +struct token { + unsigned char data[MAX_TOK_SIZE]; + unsigned char len; + /* profit: the number of bytes that could be saved by inserting this + * token into the table */ + int profit; + struct token *next; /* next token on the hash list */ + struct token *right; /* next token on the good/bad list */ + struct token *left; /* previous token on the good/bad list */ + struct token *smaller; /* token that is less one letter than this one */ + }; + +struct token bad_head, good_head; +struct token *hash_table[HASH_TABLE_SIZE]; + +/* the table that holds the result of the compression */ +unsigned char best_table[256][MAX_TOK_SIZE+1]; +unsigned char best_table_len[256]; + + static void usage(void) { @@ -59,34 +124,53 @@ read_symbol(FILE *in, struct sym_entry *s) else if (toupper(s->type) == 'A' || toupper(s->type) == 'U') return -1; - s->sym = strdup(str); + /* include the type field in the symbol name, so that it gets + * compressed together */ + s->len = strlen(str) + 1; + s->sym = (char *) malloc(s->len + 1); + strcpy(s->sym + 1, str); + s->sym[0] = s->type; + return 0; } static int symbol_valid(struct sym_entry *s) { + /* Symbols which vary between passes. Passes 1 and 2 must have + * identical symbol lists. The kallsyms_* symbols below are only added + * after pass 1, they would be included in pass 2 when --all-symbols is + * specified so exclude them to get a stable symbol list. + */ + static char *special_symbols[] = { + "kallsyms_addresses", + "kallsyms_num_syms", + "kallsyms_names", + "kallsyms_markers", + "kallsyms_token_table", + "kallsyms_token_index", + + /* Exclude linker generated symbols which vary between passes */ + "_SDA_BASE_", /* ppc */ + "_SDA2_BASE_", /* ppc */ + NULL }; + int i; + + /* if --all-symbols is not specified, then symbols outside the text + * and inittext sections are discarded */ if (!all_symbols) { if ((s->addr < _stext || s->addr > _etext) && (s->addr < _sinittext || s->addr > _einittext)) return 0; } - /* Exclude symbols which vary between passes. Passes 1 and 2 must have - * identical symbol lists. The kallsyms_* symbols below are only added - * after pass 1, they would be included in pass 2 when --all-symbols is - * specified so exclude them to get a stable symbol list. - */ - if (strstr(s->sym, "_compiled.") || - strcmp(s->sym, "kallsyms_addresses") == 0 || - strcmp(s->sym, "kallsyms_num_syms") == 0 || - strcmp(s->sym, "kallsyms_names") == 0) + /* Exclude symbols which vary between passes. */ + if (strstr(s->sym + 1, "_compiled.")) return 0; - /* Exclude linker generated symbols which vary between passes */ - if (strcmp(s->sym, "_SDA_BASE_") == 0 || /* ppc */ - strcmp(s->sym, "_SDA2_BASE_") == 0) /* ppc */ - return 0; + for (i = 0; special_symbols[i]; i++) + if( strcmp(s->sym + 1, special_symbols[i]) == 0 ) + return 0; return 1; } @@ -108,11 +192,47 @@ read_map(FILE *in) } } +static void output_label(char *label) +{ + printf(".globl %s\n",label); + printf("\tALGN\n"); + printf("%s:\n",label); +} + +/* uncompress a compressed symbol. When this function is called, the best table + * might still be compressed itself, so the function needs to be recursive */ +static int expand_symbol(unsigned char *data, int len, char *result) +{ + int c, rlen, total=0; + + while (len) { + c = *data; + /* if the table holds a single char that is the same as the one + * we are looking for, then end the search */ + if (best_table[c][0]==c && best_table_len[c]==1) { + *result++ = c; + total++; + } else { + /* if not, recurse and expand */ + rlen = expand_symbol(best_table[c], best_table_len[c], result); + total += rlen; + result += rlen; + } + data++; + len--; + } + *result=0; + + return total; +} + static void write_src(void) { - int i, valid = 0; - char *prev; + int i, k, off, valid; + unsigned int best_idx[256]; + unsigned int *markers; + char buf[KSYM_NAME_LEN+1]; printf("#include \n"); printf("#if BITS_PER_LONG == 64\n"); @@ -125,43 +245,399 @@ write_src(void) printf(".data\n"); - printf(".globl kallsyms_addresses\n"); - printf("\tALGN\n"); - printf("kallsyms_addresses:\n"); + output_label("kallsyms_addresses"); + valid = 0; for (i = 0; i < cnt; i++) { - if (!symbol_valid(&table[i])) - continue; - - printf("\tPTR\t%#llx\n", table[i].addr); - valid++; + if (table[i].flags & SYM_FLAG_VALID) { + printf("\tPTR\t%#llx\n", table[i].addr); + valid++; + } } printf("\n"); - printf(".globl kallsyms_num_syms\n"); - printf("\tALGN\n"); - printf("kallsyms_num_syms:\n"); + output_label("kallsyms_num_syms"); printf("\tPTR\t%d\n", valid); printf("\n"); - printf(".globl kallsyms_names\n"); - printf("\tALGN\n"); - printf("kallsyms_names:\n"); - prev = ""; + /* table of offset markers, that give the offset in the compressed stream + * every 256 symbols */ + markers = (unsigned int *) malloc(sizeof(unsigned int)*((valid + 255) / 256)); + + output_label("kallsyms_names"); + valid = 0; + off = 0; for (i = 0; i < cnt; i++) { - int k; - if (!symbol_valid(&table[i])) + if (!table[i].flags & SYM_FLAG_VALID) continue; - for (k = 0; table[i].sym[k] && table[i].sym[k] == prev[k]; ++k) - ; + if ((valid & 0xFF) == 0) + markers[valid >> 8] = off; - printf("\t.byte 0x%02x\n\t.asciz\t\"%s\"\n", k, table[i].sym + k); - prev = table[i].sym; + printf("\t.byte 0x%02x", table[i].len); + for (k = 0; k < table[i].len; k++) + printf(", 0x%02x", table[i].sym[k]); + printf("\n"); + + off += table[i].len + 1; + valid++; + } + printf("\n"); + + output_label("kallsyms_markers"); + for (i = 0; i < ((valid + 255) >> 8); i++) + printf("\tPTR\t%d\n", markers[i]); + printf("\n"); + + free(markers); + + output_label("kallsyms_token_table"); + off = 0; + for (i = 0; i < 256; i++) { + best_idx[i] = off; + expand_symbol(best_table[i],best_table_len[i],buf); + printf("\t.asciz\t\"%s\"\n", buf); + off += strlen(buf) + 1; } printf("\n"); + + output_label("kallsyms_token_index"); + for (i = 0; i < 256; i++) + printf("\t.short\t%d\n", best_idx[i]); + printf("\n"); +} + + +/* table lookup compression functions */ + +static inline unsigned int rehash_token(unsigned int hash, unsigned char data) +{ + return ((hash * 16777619) ^ data); +} + +static unsigned int hash_token(unsigned char *data, int len) +{ + unsigned int hash=HASH_BASE_OFFSET; + int i; + + for (i = 0; i < len; i++) + hash = rehash_token(hash, data[i]); + + return HASH_FOLD(hash); +} + +/* find a token given its data and hash value */ +static struct token *find_token_hash(unsigned char *data, int len, unsigned int hash) +{ + struct token *ptr; + + ptr = hash_table[hash]; + + while (ptr) { + if ((ptr->len == len) && (memcmp(ptr->data, data, len) == 0)) + return ptr; + ptr=ptr->next; + } + + return NULL; +} + +static inline void insert_token_in_group(struct token *head, struct token *ptr) +{ + ptr->right = head->right; + ptr->right->left = ptr; + head->right = ptr; + ptr->left = head; +} + +static inline void remove_token_from_group(struct token *ptr) +{ + ptr->left->right = ptr->right; + ptr->right->left = ptr->left; +} + + +/* build the counts for all the tokens that start with "data", and have lenghts + * from 2 to "len" */ +static void learn_token(unsigned char *data, int len) +{ + struct token *ptr,*last_ptr; + int i, newprofit; + unsigned int hash = HASH_BASE_OFFSET; + unsigned int hashes[MAX_TOK_SIZE + 1]; + + if (len > MAX_TOK_SIZE) + len = MAX_TOK_SIZE; + + /* calculate and store the hash values for all the sub-tokens */ + hash = rehash_token(hash, data[0]); + for (i = 2; i <= len; i++) { + hash = rehash_token(hash, data[i-1]); + hashes[i] = HASH_FOLD(hash); + } + + last_ptr = NULL; + ptr = NULL; + + for (i = len; i >= 2; i--) { + hash = hashes[i]; + + if (!ptr) ptr = find_token_hash(data, i, hash); + + if (!ptr) { + /* create a new token entry */ + ptr = (struct token *) malloc(sizeof(*ptr)); + + memcpy(ptr->data, data, i); + ptr->len = i; + + /* when we create an entry, it's profit is 0 because + * we also take into account the size of the token on + * the compressed table. We then subtract GOOD_BAD_THRESHOLD + * so that the test to see if this token belongs to + * the good or bad list, is a comparison to zero */ + ptr->profit = -GOOD_BAD_THRESHOLD; + + ptr->next = hash_table[hash]; + hash_table[hash] = ptr; + + insert_token_in_group(&bad_head, ptr); + + ptr->smaller = NULL; + } else { + newprofit = ptr->profit + (ptr->len - 1); + /* check to see if this token needs to be moved to a + * different list */ + if((ptr->profit < 0) && (newprofit >= 0)) { + remove_token_from_group(ptr); + insert_token_in_group(&good_head,ptr); + } + ptr->profit = newprofit; + } + + if (last_ptr) last_ptr->smaller = ptr; + last_ptr = ptr; + + ptr = ptr->smaller; + } +} + +/* decrease the counts for all the tokens that start with "data", and have lenghts + * from 2 to "len". This function is much simpler than learn_token because we have + * more guarantees (tho tokens exist, the ->smaller pointer is set, etc.) + * The two separate functions exist only because of compression performance */ +static void forget_token(unsigned char *data, int len) +{ + struct token *ptr; + int i, newprofit; + unsigned int hash=0; + + if (len > MAX_TOK_SIZE) len = MAX_TOK_SIZE; + + hash = hash_token(data, len); + ptr = find_token_hash(data, len, hash); + + for (i = len; i >= 2; i--) { + + newprofit = ptr->profit - (ptr->len - 1); + if ((ptr->profit >= 0) && (newprofit < 0)) { + remove_token_from_group(ptr); + insert_token_in_group(&bad_head, ptr); + } + ptr->profit=newprofit; + + ptr=ptr->smaller; + } } +/* count all the possible tokens in a symbol */ +static void learn_symbol(unsigned char *symbol, int len) +{ + int i; + + for (i = 0; i < len - 1; i++) + learn_token(symbol + i, len - i); +} + +/* decrease the count for all the possible tokens in a symbol */ +static void forget_symbol(unsigned char *symbol, int len) +{ + int i; + + for (i = 0; i < len - 1; i++) + forget_token(symbol + i, len - i); +} + +/* set all the symbol flags and do the initial token count */ +static void build_initial_tok_table(void) +{ + int i, use_it, valid; + + valid = 0; + for (i = 0; i < cnt; i++) { + table[i].flags = 0; + if ( symbol_valid(&table[i]) ) { + table[i].flags |= SYM_FLAG_VALID; + valid++; + } + } + + use_it = 0; + for (i = 0; i < cnt; i++) { + + /* subsample the available symbols. This method is almost like + * a Bresenham's algorithm to get uniformly distributed samples + * across the symbol table */ + if (table[i].flags & SYM_FLAG_VALID) { + + use_it += WORKING_SET; + + if (use_it >= valid) { + table[i].flags |= SYM_FLAG_SAMPLED; + use_it -= valid; + } + } + if (table[i].flags & SYM_FLAG_SAMPLED) + learn_symbol(table[i].sym, table[i].len); + } +} + +/* replace a given token in all the valid symbols. Use the sampled symbols + * to update the counts */ +static void compress_symbols(unsigned char *str, int tlen, int idx) +{ + int i, len, learn, size; + unsigned char *p; + + for (i = 0; i < cnt; i++) { + + if (!(table[i].flags & SYM_FLAG_VALID)) continue; + + len = table[i].len; + learn = 0; + p = table[i].sym; + + do { + /* find the token on the symbol */ + p = (unsigned char *) strstr((char *) p, (char *) str); + if (!p) break; + + if (!learn) { + /* if this symbol was used to count, decrease it */ + if (table[i].flags & SYM_FLAG_SAMPLED) + forget_symbol(table[i].sym, len); + learn = 1; + } + + *p = idx; + size = (len - (p - table[i].sym)) - tlen + 1; + memmove(p + 1, p + tlen, size); + p++; + len -= tlen - 1; + + } while (size >= tlen); + + if(learn) { + table[i].len = len; + /* if this symbol was used to count, learn it again */ + if(table[i].flags & SYM_FLAG_SAMPLED) + learn_symbol(table[i].sym, len); + } + } +} + +/* search the token with the maximum profit */ +static struct token *find_best_token(void) +{ + struct token *ptr,*best,*head; + int bestprofit; + + bestprofit=-10000; + + /* failsafe: if the "good" list is empty search from the "bad" list */ + if(good_head.right == &good_head) head = &bad_head; + else head = &good_head; + + ptr = head->right; + best = NULL; + while (ptr != head) { + if (ptr->profit > bestprofit) { + bestprofit = ptr->profit; + best = ptr; + } + ptr = ptr->right; + } + + return best; +} + +/* this is the core of the algorithm: calculate the "best" table */ +static void optimize_result(void) +{ + struct token *best; + int i; + + /* using the '\0' symbol last allows compress_symbols to use standard + * fast string functions */ + for (i = 255; i >= 0; i--) { + + /* if this table slot is empty (it is not used by an actual + * original char code */ + if (!best_table_len[i]) { + + /* find the token with the breates profit value */ + best = find_best_token(); + + /* place it in the "best" table */ + best_table_len[i] = best->len; + memcpy(best_table[i], best->data, best_table_len[i]); + /* zero terminate the token so that we can use strstr + in compress_symbols */ + best_table[i][best_table_len[i]]='\0'; + + /* replace this token in all the valid symbols */ + compress_symbols(best_table[i], best_table_len[i], i); + } + } +} + +/* start by placing the symbols that are actually used on the table */ +static void insert_real_symbols_in_table(void) +{ + int i, j, c; + + memset(best_table, 0, sizeof(best_table)); + memset(best_table_len, 0, sizeof(best_table_len)); + + for (i = 0; i < cnt; i++) { + if (table[i].flags & SYM_FLAG_VALID) { + for (j = 0; j < table[i].len; j++) { + c = table[i].sym[j]; + best_table[c][0]=c; + best_table_len[c]=1; + } + } + } +} + +static void optimize_token_table(void) +{ + memset(hash_table, 0, sizeof(hash_table)); + + good_head.left = &good_head; + good_head.right = &good_head; + + bad_head.left = &bad_head; + bad_head.right = &bad_head; + + build_initial_tok_table(); + + insert_real_symbols_in_table(); + + optimize_result(); +} + + int main(int argc, char **argv) { @@ -171,6 +647,7 @@ main(int argc, char **argv) usage(); read_map(stdin); + optimize_token_table(); write_src(); return 0; -- cgit v1.2.3 From d05dd6d0c370eecba901c66553a06cab6040e715 Mon Sep 17 00:00:00 2001 From: Olaf Dabrunz Date: Mon, 18 Oct 2004 17:59:16 -0700 Subject: [PATCH] TIOCCONS security The ioctl TIOCCONS allows any user to redirect console output to another tty. This allows anyone to suppress messages to the console at will. AFAIK nowadays not many programs write to /dev/console, except for start scripts and the kernel (printk() above console log level). Still, I believe that administrators and operators would not like any user to be able to hijack messages that were written to the console. The only user of TIOCCONS that I am aware of is bootlogd/blogd, which runs as root. Please comment if there are other users. Is there any reason why normal users should be able to use TIOCCONS? Otherwise I would suggest to restrict access to root (CAP_SYS_ADMIN), e.g. with this patch. Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/char/tty_io.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/char/tty_io.c b/drivers/char/tty_io.c index 89457cc2ed72..9d5e4ba9dc25 100644 --- a/drivers/char/tty_io.c +++ b/drivers/char/tty_io.c @@ -1981,10 +1981,10 @@ static int tiocswinsz(struct tty_struct *tty, struct tty_struct *real_tty, static int tioccons(struct file *file) { + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; if (file->f_op->write == redirected_tty_write) { struct file *f; - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; spin_lock(&redirect_lock); f = redirect; redirect = NULL; -- cgit v1.2.3 From d79889923f9c04f6f5592851dc3033236756c5dc Mon Sep 17 00:00:00 2001 From: William Lee Irwin III Date: Mon, 18 Oct 2004 17:59:28 -0700 Subject: [PATCH] move waitqueue functions to kernel/wait.c The following patch series consolidates the various instances of waitqueue hashing to use a uniform structure and share the per-zone hashtable among all waitqueue hashers. This is expected to increase the number of hashtable buckets available for waiting on bh's and inodes and eliminate statically allocated kernel data structures for greater node locality and reduced kernel image size. Some attempt was made to look similar to Oleg Nesterov's suggested API in order to provide some kind of credit for independent invention of something very similar (the original versions of these patches predated my public postings on the subject of filtered waitqueues). These patches have the further benefit and intention of enabling aio to use filtered wakeups by standardizing the data structure passed to wake functions so that embedded waitqueue elements in aio structures may be succesfully passed to the filtered wakeup wake functions, though this patch series doesn't implement that particular functionality. Successfully stress-tested on x86-64, and ia64 in recent prior versions. This patch: Move waitqueue -related functions not needing static functions in sched.c to kernel/wait.c Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- kernel/Makefile | 2 +- kernel/fork.c | 125 ------------------------------------------------------ kernel/wait.c | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 126 deletions(-) create mode 100644 kernel/wait.c diff --git a/kernel/Makefile b/kernel/Makefile index fd79e9348117..b9b2e10da379 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -7,7 +7,7 @@ obj-y = sched.o fork.o exec_domain.o panic.o printk.o profile.o \ sysctl.o capability.o ptrace.o timer.o user.o \ signal.o sys.o kmod.o workqueue.o pid.o \ rcupdate.o intermodule.o extable.o params.o posix-timers.o \ - kthread.o + kthread.o wait.o obj-$(CONFIG_FUTEX) += futex.o obj-$(CONFIG_GENERIC_ISA_DMA) += dma.o diff --git a/kernel/fork.c b/kernel/fork.c index 2502791f0ba4..bb28c1f1e551 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -101,131 +101,6 @@ void __put_task_struct(struct task_struct *tsk) free_task(tsk); } -void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t * wait) -{ - unsigned long flags; - - wait->flags &= ~WQ_FLAG_EXCLUSIVE; - spin_lock_irqsave(&q->lock, flags); - __add_wait_queue(q, wait); - spin_unlock_irqrestore(&q->lock, flags); -} - -EXPORT_SYMBOL(add_wait_queue); - -void fastcall add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t * wait) -{ - unsigned long flags; - - wait->flags |= WQ_FLAG_EXCLUSIVE; - spin_lock_irqsave(&q->lock, flags); - __add_wait_queue_tail(q, wait); - spin_unlock_irqrestore(&q->lock, flags); -} - -EXPORT_SYMBOL(add_wait_queue_exclusive); - -void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t * wait) -{ - unsigned long flags; - - spin_lock_irqsave(&q->lock, flags); - __remove_wait_queue(q, wait); - spin_unlock_irqrestore(&q->lock, flags); -} - -EXPORT_SYMBOL(remove_wait_queue); - - -/* - * Note: we use "set_current_state()" _after_ the wait-queue add, - * because we need a memory barrier there on SMP, so that any - * wake-function that tests for the wait-queue being active - * will be guaranteed to see waitqueue addition _or_ subsequent - * tests in this thread will see the wakeup having taken place. - * - * The spin_unlock() itself is semi-permeable and only protects - * one way (it only protects stuff inside the critical region and - * stops them from bleeding out - it would still allow subsequent - * loads to move into the the critical region). - */ -void fastcall prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state) -{ - unsigned long flags; - - wait->flags &= ~WQ_FLAG_EXCLUSIVE; - spin_lock_irqsave(&q->lock, flags); - if (list_empty(&wait->task_list)) - __add_wait_queue(q, wait); - /* - * don't alter the task state if this is just going to - * queue an async wait queue callback - */ - if (is_sync_wait(wait)) - set_current_state(state); - spin_unlock_irqrestore(&q->lock, flags); -} - -EXPORT_SYMBOL(prepare_to_wait); - -void fastcall -prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state) -{ - unsigned long flags; - - wait->flags |= WQ_FLAG_EXCLUSIVE; - spin_lock_irqsave(&q->lock, flags); - if (list_empty(&wait->task_list)) - __add_wait_queue_tail(q, wait); - /* - * don't alter the task state if this is just going to - * queue an async wait queue callback - */ - if (is_sync_wait(wait)) - set_current_state(state); - spin_unlock_irqrestore(&q->lock, flags); -} - -EXPORT_SYMBOL(prepare_to_wait_exclusive); - -void fastcall finish_wait(wait_queue_head_t *q, wait_queue_t *wait) -{ - unsigned long flags; - - __set_current_state(TASK_RUNNING); - /* - * We can check for list emptiness outside the lock - * IFF: - * - we use the "careful" check that verifies both - * the next and prev pointers, so that there cannot - * be any half-pending updates in progress on other - * CPU's that we haven't seen yet (and that might - * still change the stack area. - * and - * - all other users take the lock (ie we can only - * have _one_ other CPU that looks at or modifies - * the list). - */ - if (!list_empty_careful(&wait->task_list)) { - spin_lock_irqsave(&q->lock, flags); - list_del_init(&wait->task_list); - spin_unlock_irqrestore(&q->lock, flags); - } -} - -EXPORT_SYMBOL(finish_wait); - -int autoremove_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key) -{ - int ret = default_wake_function(wait, mode, sync, key); - - if (ret) - list_del_init(&wait->task_list); - return ret; -} - -EXPORT_SYMBOL(autoremove_wake_function); - void __init fork_init(unsigned long mempages) { #ifndef __HAVE_ARCH_TASK_STRUCT_ALLOCATOR diff --git a/kernel/wait.c b/kernel/wait.c new file mode 100644 index 000000000000..923a47e7c027 --- /dev/null +++ b/kernel/wait.c @@ -0,0 +1,129 @@ +/* + * Generic waiting primitives. + * + * (C) 2004 William Irwin, Oracle + */ +#include +#include +#include +#include +#include + +void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait) +{ + unsigned long flags; + + wait->flags &= ~WQ_FLAG_EXCLUSIVE; + spin_lock_irqsave(&q->lock, flags); + __add_wait_queue(q, wait); + spin_unlock_irqrestore(&q->lock, flags); +} +EXPORT_SYMBOL(add_wait_queue); + +void fastcall add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait) +{ + unsigned long flags; + + wait->flags |= WQ_FLAG_EXCLUSIVE; + spin_lock_irqsave(&q->lock, flags); + __add_wait_queue_tail(q, wait); + spin_unlock_irqrestore(&q->lock, flags); +} +EXPORT_SYMBOL(add_wait_queue_exclusive); + +void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait) +{ + unsigned long flags; + + spin_lock_irqsave(&q->lock, flags); + __remove_wait_queue(q, wait); + spin_unlock_irqrestore(&q->lock, flags); +} +EXPORT_SYMBOL(remove_wait_queue); + + +/* + * Note: we use "set_current_state()" _after_ the wait-queue add, + * because we need a memory barrier there on SMP, so that any + * wake-function that tests for the wait-queue being active + * will be guaranteed to see waitqueue addition _or_ subsequent + * tests in this thread will see the wakeup having taken place. + * + * The spin_unlock() itself is semi-permeable and only protects + * one way (it only protects stuff inside the critical region and + * stops them from bleeding out - it would still allow subsequent + * loads to move into the the critical region). + */ +void fastcall +prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state) +{ + unsigned long flags; + + wait->flags &= ~WQ_FLAG_EXCLUSIVE; + spin_lock_irqsave(&q->lock, flags); + if (list_empty(&wait->task_list)) + __add_wait_queue(q, wait); + /* + * don't alter the task state if this is just going to + * queue an async wait queue callback + */ + if (is_sync_wait(wait)) + set_current_state(state); + spin_unlock_irqrestore(&q->lock, flags); +} +EXPORT_SYMBOL(prepare_to_wait); + +void fastcall +prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state) +{ + unsigned long flags; + + wait->flags |= WQ_FLAG_EXCLUSIVE; + spin_lock_irqsave(&q->lock, flags); + if (list_empty(&wait->task_list)) + __add_wait_queue_tail(q, wait); + /* + * don't alter the task state if this is just going to + * queue an async wait queue callback + */ + if (is_sync_wait(wait)) + set_current_state(state); + spin_unlock_irqrestore(&q->lock, flags); +} +EXPORT_SYMBOL(prepare_to_wait_exclusive); + +void fastcall finish_wait(wait_queue_head_t *q, wait_queue_t *wait) +{ + unsigned long flags; + + __set_current_state(TASK_RUNNING); + /* + * We can check for list emptiness outside the lock + * IFF: + * - we use the "careful" check that verifies both + * the next and prev pointers, so that there cannot + * be any half-pending updates in progress on other + * CPU's that we haven't seen yet (and that might + * still change the stack area. + * and + * - all other users take the lock (ie we can only + * have _one_ other CPU that looks at or modifies + * the list). + */ + if (!list_empty_careful(&wait->task_list)) { + spin_lock_irqsave(&q->lock, flags); + list_del_init(&wait->task_list); + spin_unlock_irqrestore(&q->lock, flags); + } +} +EXPORT_SYMBOL(finish_wait); + +int autoremove_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key) +{ + int ret = default_wake_function(wait, mode, sync, key); + + if (ret) + list_del_init(&wait->task_list); + return ret; +} +EXPORT_SYMBOL(autoremove_wake_function); -- cgit v1.2.3 From fd4d36bf0d54e0b020b8ffeddf7552562eab17c5 Mon Sep 17 00:00:00 2001 From: William Lee Irwin III Date: Mon, 18 Oct 2004 17:59:41 -0700 Subject: [PATCH] standardize bit waiting data type Eliminate specialized page and bh waitqueue hashing structures in favor of a standardized structure, using wake_up_bit() to wake waiters using the standardized wait_bit_key structure. Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/buffer.c | 52 +++++++------------------------------------ include/linux/wait.h | 26 ++++++++++++++++++++++ kernel/wait.c | 23 +++++++++++++++++++ mm/filemap.c | 63 ++++++++-------------------------------------------- 4 files changed, 66 insertions(+), 98 deletions(-) diff --git a/fs/buffer.c b/fs/buffer.c index 4ec2acb57946..6eeafe142756 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -45,26 +45,6 @@ static void invalidate_bh_lrus(void); #define BH_ENTRY(list) list_entry((list), struct buffer_head, b_assoc_buffers) -struct bh_wait_queue { - struct buffer_head *bh; - wait_queue_t wait; -}; - -#define __DEFINE_BH_WAIT(name, b, f) \ - struct bh_wait_queue name = { \ - .bh = b, \ - .wait = { \ - .task = current, \ - .flags = f, \ - .func = bh_wake_function, \ - .task_list = \ - LIST_HEAD_INIT(name.wait.task_list),\ - }, \ - } -#define DEFINE_BH_WAIT(name, bh) __DEFINE_BH_WAIT(name, bh, 0) -#define DEFINE_BH_WAIT_EXCLUSIVE(name, bh) \ - __DEFINE_BH_WAIT(name, bh, WQ_FLAG_EXCLUSIVE) - /* * Hashed waitqueue_head's for wait_on_buffer() */ @@ -95,24 +75,10 @@ void wake_up_buffer(struct buffer_head *bh) wait_queue_head_t *wq = bh_waitq_head(bh); smp_mb(); - if (waitqueue_active(wq)) - __wake_up(wq, TASK_INTERRUPTIBLE|TASK_UNINTERRUPTIBLE, 1, bh); + __wake_up_bit(wq, &bh->b_state, BH_Lock); } EXPORT_SYMBOL(wake_up_buffer); -static int bh_wake_function(wait_queue_t *wait, unsigned mode, - int sync, void *key) -{ - struct buffer_head *bh = key; - struct bh_wait_queue *wq; - - wq = container_of(wait, struct bh_wait_queue, wait); - if (wq->bh != bh || buffer_locked(bh)) - return 0; - else - return autoremove_wake_function(wait, mode, sync, key); -} - static void sync_buffer(struct buffer_head *bh) { struct block_device *bd; @@ -126,7 +92,7 @@ static void sync_buffer(struct buffer_head *bh) void fastcall __lock_buffer(struct buffer_head *bh) { wait_queue_head_t *wqh = bh_waitq_head(bh); - DEFINE_BH_WAIT_EXCLUSIVE(wait, bh); + DEFINE_WAIT_BIT(wait, &bh->b_state, BH_Lock); do { prepare_to_wait_exclusive(wqh, &wait.wait, @@ -155,15 +121,13 @@ void fastcall unlock_buffer(struct buffer_head *bh) void __wait_on_buffer(struct buffer_head * bh) { wait_queue_head_t *wqh = bh_waitq_head(bh); - DEFINE_BH_WAIT(wait, bh); + DEFINE_WAIT_BIT(wait, &bh->b_state, BH_Lock); - do { - prepare_to_wait(wqh, &wait.wait, TASK_UNINTERRUPTIBLE); - if (buffer_locked(bh)) { - sync_buffer(bh); - io_schedule(); - } - } while (buffer_locked(bh)); + prepare_to_wait(wqh, &wait.wait, TASK_UNINTERRUPTIBLE); + if (buffer_locked(bh)) { + sync_buffer(bh); + io_schedule(); + } finish_wait(wqh, &wait.wait); } diff --git a/include/linux/wait.h b/include/linux/wait.h index 21cd4df67b24..82068628327f 100644 --- a/include/linux/wait.h +++ b/include/linux/wait.h @@ -37,6 +37,16 @@ struct __wait_queue { struct list_head task_list; }; +struct wait_bit_key { + void *flags; + int bit_nr; +}; + +struct wait_bit_queue { + struct wait_bit_key key; + wait_queue_t wait; +}; + struct __wait_queue_head { spinlock_t lock; struct list_head task_list; @@ -63,6 +73,9 @@ typedef struct __wait_queue_head wait_queue_head_t; #define DECLARE_WAIT_QUEUE_HEAD(name) \ wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name) +#define __WAIT_BIT_KEY_INITIALIZER(word, bit) \ + { .flags = word, .bit_nr = bit, } + static inline void init_waitqueue_head(wait_queue_head_t *q) { q->lock = SPIN_LOCK_UNLOCKED; @@ -125,6 +138,7 @@ static inline void __remove_wait_queue(wait_queue_head_t *head, void FASTCALL(__wake_up(wait_queue_head_t *q, unsigned int mode, int nr, void *key)); extern void FASTCALL(__wake_up_locked(wait_queue_head_t *q, unsigned int mode)); extern void FASTCALL(__wake_up_sync(wait_queue_head_t *q, unsigned int mode, int nr)); +void FASTCALL(__wake_up_bit(wait_queue_head_t *, void *, int)); #define wake_up(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1, NULL) #define wake_up_nr(x, nr) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, nr, NULL) @@ -300,6 +314,7 @@ void FASTCALL(prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state)); void FASTCALL(finish_wait(wait_queue_head_t *q, wait_queue_t *wait)); int autoremove_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key); +int wake_bit_function(wait_queue_t *wait, unsigned mode, int sync, void *key); #define DEFINE_WAIT(name) \ wait_queue_t name = { \ @@ -310,6 +325,17 @@ int autoremove_wake_function(wait_queue_t *wait, unsigned mode, int sync, void * }, \ } +#define DEFINE_WAIT_BIT(name, word, bit) \ + struct wait_bit_queue name = { \ + .key = __WAIT_BIT_KEY_INITIALIZER(word, bit), \ + .wait = { \ + .task = current, \ + .func = wake_bit_function, \ + .task_list = \ + LIST_HEAD_INIT(name.wait.task_list), \ + }, \ + } + #define init_wait(wait) \ do { \ wait->task = current; \ diff --git a/kernel/wait.c b/kernel/wait.c index 923a47e7c027..78256a812ca0 100644 --- a/kernel/wait.c +++ b/kernel/wait.c @@ -127,3 +127,26 @@ int autoremove_wake_function(wait_queue_t *wait, unsigned mode, int sync, void * return ret; } EXPORT_SYMBOL(autoremove_wake_function); + +int wake_bit_function(wait_queue_t *wait, unsigned mode, int sync, void *arg) +{ + struct wait_bit_key *key = arg; + struct wait_bit_queue *wait_bit + = container_of(wait, struct wait_bit_queue, wait); + + if (wait_bit->key.flags != key->flags || + wait_bit->key.bit_nr != key->bit_nr || + test_bit(key->bit_nr, key->flags)) + return 0; + else + return autoremove_wake_function(wait, mode, sync, key); +} +EXPORT_SYMBOL(wake_bit_function); + +void fastcall __wake_up_bit(wait_queue_head_t *wq, void *word, int bit) +{ + struct wait_bit_key key = __WAIT_BIT_KEY_INITIALIZER(word, bit); + if (waitqueue_active(wq)) + __wake_up(wq, TASK_INTERRUPTIBLE|TASK_UNINTERRUPTIBLE, 1, &key); +} +EXPORT_SYMBOL(__wake_up_bit); diff --git a/mm/filemap.c b/mm/filemap.c index 3935097dc5cb..6318325e24ca 100644 --- a/mm/filemap.c +++ b/mm/filemap.c @@ -360,40 +360,6 @@ int add_to_page_cache_lru(struct page *page, struct address_space *mapping, * at a cost of "thundering herd" phenomena during rare hash * collisions. */ -struct page_wait_queue { - struct page *page; - int bit; - wait_queue_t wait; -}; - -static int page_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key) -{ - struct page *page = key; - struct page_wait_queue *wq; - - wq = container_of(wait, struct page_wait_queue, wait); - if (wq->page != page || test_bit(wq->bit, &page->flags)) - return 0; - else - return autoremove_wake_function(wait, mode, sync, NULL); -} - -#define __DEFINE_PAGE_WAIT(name, p, b, f) \ - struct page_wait_queue name = { \ - .page = p, \ - .bit = b, \ - .wait = { \ - .task = current, \ - .func = page_wake_function, \ - .flags = f, \ - .task_list = LIST_HEAD_INIT(name.wait.task_list),\ - }, \ - } - -#define DEFINE_PAGE_WAIT(name, p, b) __DEFINE_PAGE_WAIT(name, p, b, 0) -#define DEFINE_PAGE_WAIT_EXCLUSIVE(name, p, b) \ - __DEFINE_PAGE_WAIT(name, p, b, WQ_FLAG_EXCLUSIVE) - static wait_queue_head_t *page_waitqueue(struct page *page) { const struct zone *zone = page_zone(page); @@ -401,27 +367,16 @@ static wait_queue_head_t *page_waitqueue(struct page *page) return &zone->wait_table[hash_ptr(page, zone->wait_table_bits)]; } -static void wake_up_page(struct page *page) -{ - const unsigned int mode = TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE; - wait_queue_head_t *waitqueue = page_waitqueue(page); - - if (waitqueue_active(waitqueue)) - __wake_up(waitqueue, mode, 1, page); -} - void fastcall wait_on_page_bit(struct page *page, int bit_nr) { wait_queue_head_t *waitqueue = page_waitqueue(page); - DEFINE_PAGE_WAIT(wait, page, bit_nr); + DEFINE_WAIT_BIT(wait, &page->flags, bit_nr); - do { - prepare_to_wait(waitqueue, &wait.wait, TASK_UNINTERRUPTIBLE); - if (test_bit(bit_nr, &page->flags)) { - sync_page(page); - io_schedule(); - } - } while (test_bit(bit_nr, &page->flags)); + prepare_to_wait(waitqueue, &wait.wait, TASK_UNINTERRUPTIBLE); + if (test_bit(bit_nr, &page->flags)) { + sync_page(page); + io_schedule(); + } finish_wait(waitqueue, &wait.wait); } @@ -448,7 +403,7 @@ void fastcall unlock_page(struct page *page) if (!TestClearPageLocked(page)) BUG(); smp_mb__after_clear_bit(); - wake_up_page(page); + __wake_up_bit(page_waitqueue(page), &page->flags, PG_locked); } EXPORT_SYMBOL(unlock_page); @@ -464,7 +419,7 @@ void end_page_writeback(struct page *page) BUG(); smp_mb__after_clear_bit(); } - wake_up_page(page); + __wake_up_bit(page_waitqueue(page), &page->flags, PG_writeback); } EXPORT_SYMBOL(end_page_writeback); @@ -480,7 +435,7 @@ EXPORT_SYMBOL(end_page_writeback); void fastcall __lock_page(struct page *page) { wait_queue_head_t *wqh = page_waitqueue(page); - DEFINE_PAGE_WAIT_EXCLUSIVE(wait, page, PG_locked); + DEFINE_WAIT_BIT(wait, &page->flags, PG_locked); while (TestSetPageLocked(page)) { prepare_to_wait_exclusive(wqh, &wait.wait, TASK_UNINTERRUPTIBLE); -- cgit v1.2.3 From baa896b3ded47a7f6a401267f5995dcc09d5d5d4 Mon Sep 17 00:00:00 2001 From: William Lee Irwin III Date: Mon, 18 Oct 2004 17:59:53 -0700 Subject: [PATCH] consolidate bit waiting code patterns Consolidate bit waiting code patterns for page waitqueues using __wait_on_bit() and __wait_on_bit_lock(). Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- include/linux/wait.h | 2 ++ kernel/wait.c | 37 +++++++++++++++++++++++++++++++++++++ mm/filemap.c | 40 ++++++++++++++++++---------------------- 3 files changed, 57 insertions(+), 22 deletions(-) diff --git a/include/linux/wait.h b/include/linux/wait.h index 82068628327f..d9dfd7e32e7b 100644 --- a/include/linux/wait.h +++ b/include/linux/wait.h @@ -139,6 +139,8 @@ void FASTCALL(__wake_up(wait_queue_head_t *q, unsigned int mode, int nr, void *k extern void FASTCALL(__wake_up_locked(wait_queue_head_t *q, unsigned int mode)); extern void FASTCALL(__wake_up_sync(wait_queue_head_t *q, unsigned int mode, int nr)); void FASTCALL(__wake_up_bit(wait_queue_head_t *, void *, int)); +int FASTCALL(__wait_on_bit(wait_queue_head_t *, struct wait_bit_queue *, void *, int, int (*)(void *), unsigned)); +int FASTCALL(__wait_on_bit_lock(wait_queue_head_t *, struct wait_bit_queue *, void *, int, int (*)(void *), unsigned)); #define wake_up(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1, NULL) #define wake_up_nr(x, nr) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, nr, NULL) diff --git a/kernel/wait.c b/kernel/wait.c index 78256a812ca0..29057f707dbd 100644 --- a/kernel/wait.c +++ b/kernel/wait.c @@ -143,6 +143,43 @@ int wake_bit_function(wait_queue_t *wait, unsigned mode, int sync, void *arg) } EXPORT_SYMBOL(wake_bit_function); +/* + * To allow interruptible waiting and asynchronous (i.e. nonblocking) + * waiting, the actions of __wait_on_bit() and __wait_on_bit_lock() are + * permitted return codes. Nonzero return codes halt waiting and return. + */ +int __sched fastcall +__wait_on_bit(wait_queue_head_t *wq, struct wait_bit_queue *q, + void *word, int bit, int (*action)(void *), unsigned mode) +{ + int ret = 0; + + prepare_to_wait(wq, &q->wait, mode); + if (test_bit(bit, word)) + ret = (*action)(word); + finish_wait(wq, &q->wait); + return ret; +} +EXPORT_SYMBOL(__wait_on_bit); + +int __sched fastcall +__wait_on_bit_lock(wait_queue_head_t *wq, struct wait_bit_queue *q, + void *word, int bit, int (*action)(void *), unsigned mode) +{ + int ret = 0; + + while (test_and_set_bit(bit, word)) { + prepare_to_wait_exclusive(wq, &q->wait, mode); + if (test_bit(bit, word)) { + if ((ret = (*action)(word))) + break; + } + } + finish_wait(wq, &q->wait); + return ret; +} +EXPORT_SYMBOL(__wait_on_bit_lock); + void fastcall __wake_up_bit(wait_queue_head_t *wq, void *word, int bit) { struct wait_bit_key key = __WAIT_BIT_KEY_INITIALIZER(word, bit); diff --git a/mm/filemap.c b/mm/filemap.c index 6318325e24ca..14159485b57c 100644 --- a/mm/filemap.c +++ b/mm/filemap.c @@ -131,9 +131,12 @@ void remove_from_page_cache(struct page *page) spin_unlock_irq(&mapping->tree_lock); } -static inline int sync_page(struct page *page) +static int sync_page(void *word) { struct address_space *mapping; + struct page *page; + + page = container_of((page_flags_t *)word, struct page, flags); /* * FIXME, fercrissake. What is this barrier here for? @@ -141,7 +144,8 @@ static inline int sync_page(struct page *page) smp_mb(); mapping = page_mapping(page); if (mapping && mapping->a_ops && mapping->a_ops->sync_page) - return mapping->a_ops->sync_page(page); + mapping->a_ops->sync_page(page); + io_schedule(); return 0; } @@ -367,19 +371,19 @@ static wait_queue_head_t *page_waitqueue(struct page *page) return &zone->wait_table[hash_ptr(page, zone->wait_table_bits)]; } +static inline void wake_up_page(struct page *page, int bit) +{ + __wake_up_bit(page_waitqueue(page), &page->flags, bit); +} + void fastcall wait_on_page_bit(struct page *page, int bit_nr) { - wait_queue_head_t *waitqueue = page_waitqueue(page); DEFINE_WAIT_BIT(wait, &page->flags, bit_nr); - prepare_to_wait(waitqueue, &wait.wait, TASK_UNINTERRUPTIBLE); - if (test_bit(bit_nr, &page->flags)) { - sync_page(page); - io_schedule(); - } - finish_wait(waitqueue, &wait.wait); + if (test_bit(bit_nr, &page->flags)) + __wait_on_bit(page_waitqueue(page), &wait, wait.key.flags, + bit_nr, sync_page, TASK_UNINTERRUPTIBLE); } - EXPORT_SYMBOL(wait_on_page_bit); /** @@ -403,7 +407,7 @@ void fastcall unlock_page(struct page *page) if (!TestClearPageLocked(page)) BUG(); smp_mb__after_clear_bit(); - __wake_up_bit(page_waitqueue(page), &page->flags, PG_locked); + wake_up_page(page, PG_locked); } EXPORT_SYMBOL(unlock_page); @@ -419,7 +423,7 @@ void end_page_writeback(struct page *page) BUG(); smp_mb__after_clear_bit(); } - __wake_up_bit(page_waitqueue(page), &page->flags, PG_writeback); + wake_up_page(page, PG_writeback); } EXPORT_SYMBOL(end_page_writeback); @@ -434,19 +438,11 @@ EXPORT_SYMBOL(end_page_writeback); */ void fastcall __lock_page(struct page *page) { - wait_queue_head_t *wqh = page_waitqueue(page); DEFINE_WAIT_BIT(wait, &page->flags, PG_locked); - while (TestSetPageLocked(page)) { - prepare_to_wait_exclusive(wqh, &wait.wait, TASK_UNINTERRUPTIBLE); - if (PageLocked(page)) { - sync_page(page); - io_schedule(); - } - } - finish_wait(wqh, &wait.wait); + __wait_on_bit_lock(page_waitqueue(page), &wait, wait.key.flags, + PG_locked, sync_page, TASK_UNINTERRUPTIBLE); } - EXPORT_SYMBOL(__lock_page); /* -- cgit v1.2.3 From 525b64cdbd0401b8d3cb5642159e5ec8e49290b7 Mon Sep 17 00:00:00 2001 From: William Lee Irwin III Date: Mon, 18 Oct 2004 18:00:05 -0700 Subject: [PATCH] eliminate bh waitqueue hashtable Eliminate the bh waitqueue hashtable using bit_waitqueue() via wait_on_bit() and wake_up_bit() to locate the waitqueue head associated with a bit. Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/buffer.c | 55 +++++++-------------------------------- fs/jbd/transaction.c | 10 +++---- include/linux/wait.h | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++ kernel/wait.c | 11 ++++++++ 4 files changed, 98 insertions(+), 51 deletions(-) diff --git a/fs/buffer.c b/fs/buffer.c index 6eeafe142756..a8cfc265ec64 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -45,14 +45,6 @@ static void invalidate_bh_lrus(void); #define BH_ENTRY(list) list_entry((list), struct buffer_head, b_assoc_buffers) -/* - * Hashed waitqueue_head's for wait_on_buffer() - */ -#define BH_WAIT_TABLE_ORDER 7 -static struct bh_wait_queue_head { - wait_queue_head_t wqh; -} ____cacheline_aligned_in_smp bh_wait_queue_heads[1<b_private = private; } -/* - * Return the address of the waitqueue_head to be used for this - * buffer_head - */ -wait_queue_head_t *bh_waitq_head(struct buffer_head *bh) -{ - return &bh_wait_queue_heads[hash_ptr(bh, BH_WAIT_TABLE_ORDER)].wqh; -} -EXPORT_SYMBOL(bh_waitq_head); - void wake_up_buffer(struct buffer_head *bh) { - wait_queue_head_t *wq = bh_waitq_head(bh); - smp_mb(); - __wake_up_bit(wq, &bh->b_state, BH_Lock); + wake_up_bit(&bh->b_state, BH_Lock); } EXPORT_SYMBOL(wake_up_buffer); -static void sync_buffer(struct buffer_head *bh) +static int sync_buffer(void *word) { struct block_device *bd; + struct buffer_head *bh + = container_of(word, struct buffer_head, b_state); smp_mb(); bd = bh->b_bdev; if (bd) blk_run_address_space(bd->bd_inode->i_mapping); + io_schedule(); + return 0; } void fastcall __lock_buffer(struct buffer_head *bh) { - wait_queue_head_t *wqh = bh_waitq_head(bh); - DEFINE_WAIT_BIT(wait, &bh->b_state, BH_Lock); - - do { - prepare_to_wait_exclusive(wqh, &wait.wait, - TASK_UNINTERRUPTIBLE); - if (buffer_locked(bh)) { - sync_buffer(bh); - io_schedule(); - } - } while (test_set_buffer_locked(bh)); - finish_wait(wqh, &wait.wait); + wait_on_bit_lock(&bh->b_state, BH_Lock, sync_buffer, + TASK_UNINTERRUPTIBLE); } EXPORT_SYMBOL(__lock_buffer); @@ -120,15 +94,7 @@ void fastcall unlock_buffer(struct buffer_head *bh) */ void __wait_on_buffer(struct buffer_head * bh) { - wait_queue_head_t *wqh = bh_waitq_head(bh); - DEFINE_WAIT_BIT(wait, &bh->b_state, BH_Lock); - - prepare_to_wait(wqh, &wait.wait, TASK_UNINTERRUPTIBLE); - if (buffer_locked(bh)) { - sync_buffer(bh); - io_schedule(); - } - finish_wait(wqh, &wait.wait); + wait_on_bit(&bh->b_state, BH_Lock, sync_buffer, TASK_UNINTERRUPTIBLE); } static void @@ -3087,14 +3053,11 @@ static int buffer_cpu_notify(struct notifier_block *self, void __init buffer_init(void) { - int i; int nrpages; bh_cachep = kmem_cache_create("buffer_head", sizeof(struct buffer_head), 0, SLAB_PANIC, init_buffer_head, NULL); - for (i = 0; i < ARRAY_SIZE(bh_wait_queue_heads); i++) - init_waitqueue_head(&bh_wait_queue_heads[i].wqh); /* * Limit the bh occupancy to 10% of ZONE_NORMAL diff --git a/fs/jbd/transaction.c b/fs/jbd/transaction.c index 18a678ce2591..304165faa19d 100644 --- a/fs/jbd/transaction.c +++ b/fs/jbd/transaction.c @@ -633,21 +633,21 @@ repeat: * disk then we cannot do copy-out here. */ if (jh->b_jlist == BJ_Shadow) { - wait_queue_head_t *wqh; - DEFINE_WAIT(wait); + DEFINE_WAIT_BIT(wait, &bh->b_state, BH_Lock); + wait_queue_head_t *wqh + = bit_waitqueue(&bh->b_state, BH_Lock); JBUFFER_TRACE(jh, "on shadow: sleep"); jbd_unlock_bh_state(bh); /* commit wakes up all shadow buffers after IO */ - wqh = bh_waitq_head(bh); for ( ; ; ) { - prepare_to_wait(wqh, &wait, + prepare_to_wait(wqh, &wait.wait, TASK_UNINTERRUPTIBLE); if (jh->b_jlist != BJ_Shadow) break; schedule(); } - finish_wait(wqh, &wait); + finish_wait(wqh, &wait.wait); goto repeat; } diff --git a/include/linux/wait.h b/include/linux/wait.h index d9dfd7e32e7b..f58a313cc5b3 100644 --- a/include/linux/wait.h +++ b/include/linux/wait.h @@ -24,6 +24,7 @@ #include #include #include +#include typedef struct __wait_queue wait_queue_t; typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int sync, void *key); @@ -141,6 +142,22 @@ extern void FASTCALL(__wake_up_sync(wait_queue_head_t *q, unsigned int mode, int void FASTCALL(__wake_up_bit(wait_queue_head_t *, void *, int)); int FASTCALL(__wait_on_bit(wait_queue_head_t *, struct wait_bit_queue *, void *, int, int (*)(void *), unsigned)); int FASTCALL(__wait_on_bit_lock(wait_queue_head_t *, struct wait_bit_queue *, void *, int, int (*)(void *), unsigned)); +wait_queue_head_t *FASTCALL(bit_waitqueue(void *, int)); + +/** + * wake_up_bit - wake up a waiter on a bit + * @word: the word being waited on, a kernel virtual address + * @bit: the bit of the word being waited on + * + * There is a standard hashed waitqueue table for generic use. This + * is the part of the hashtable's accessor API that wakes up waiters + * on a bit. For instance, if one were to have waiters on a bitflag, + * one would call wake_up_bit() after clearing the bit. + */ +static inline void wake_up_bit(void *word, int bit) +{ + __wake_up_bit(bit_waitqueue(word, bit), word, bit); +} #define wake_up(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1, NULL) #define wake_up_nr(x, nr) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, nr, NULL) @@ -344,6 +361,62 @@ int wake_bit_function(wait_queue_t *wait, unsigned mode, int sync, void *key); wait->func = autoremove_wake_function; \ INIT_LIST_HEAD(&wait->task_list); \ } while (0) + +/** + * wait_on_bit - wait for a bit to be cleared + * @word: the word being waited on, a kernel virtual address + * @bit: the bit of the word being waited on + * @action: the function used to sleep, which may take special actions + * @mode: the task state to sleep in + * + * There is a standard hashed waitqueue table for generic use. This + * is the part of the hashtable's accessor API that waits on a bit. + * For instance, if one were to have waiters on a bitflag, one would + * call wait_on_bit() in threads waiting for the bit to clear. + * One uses wait_on_bit() where one is waiting for the bit to clear, + * but has no intention of setting it. + */ +static inline int wait_on_bit(void *word, int bit, + int (*action)(void *), unsigned mode) +{ + DEFINE_WAIT_BIT(q, word, bit); + wait_queue_head_t *wqh; + + if (!test_and_set_bit(bit, word)) + return 0; + + wqh = bit_waitqueue(word, bit); + return __wait_on_bit(wqh, &q, word, bit, action, mode); +} + +/** + * wait_on_bit_lock - wait for a bit to be cleared, when wanting to set it + * @word: the word being waited on, a kernel virtual address + * @bit: the bit of the word being waited on + * @action: the function used to sleep, which may take special actions + * @mode: the task state to sleep in + * + * There is a standard hashed waitqueue table for generic use. This + * is the part of the hashtable's accessor API that waits on a bit + * when one intends to set it, for instance, trying to lock bitflags. + * For instance, if one were to have waiters trying to set bitflag + * and waiting for it to clear before setting it, one would call + * wait_on_bit() in threads waiting to be able to set the bit. + * One uses wait_on_bit_lock() where one is waiting for the bit to + * clear with the intention of setting it, and when done, clearing it. + */ +static inline int wait_on_bit_lock(void *word, int bit, + int (*action)(void *), unsigned mode) +{ + DEFINE_WAIT_BIT(q, word, bit); + wait_queue_head_t *wqh; + + if (!test_bit(bit, word)) + return 0; + + wqh = bit_waitqueue(word, bit); + return __wait_on_bit_lock(wqh, &q, word, bit, action, mode); +} #endif /* __KERNEL__ */ diff --git a/kernel/wait.c b/kernel/wait.c index 29057f707dbd..e87ae721643c 100644 --- a/kernel/wait.c +++ b/kernel/wait.c @@ -8,6 +8,7 @@ #include #include #include +#include void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait) { @@ -187,3 +188,13 @@ void fastcall __wake_up_bit(wait_queue_head_t *wq, void *word, int bit) __wake_up(wq, TASK_INTERRUPTIBLE|TASK_UNINTERRUPTIBLE, 1, &key); } EXPORT_SYMBOL(__wake_up_bit); + +fastcall wait_queue_head_t *bit_waitqueue(void *word, int bit) +{ + const int shift = BITS_PER_LONG == 32 ? 5 : 6; + const struct zone *zone = page_zone(virt_to_page(word)); + unsigned long val = (unsigned long)word << shift | bit; + + return &zone->wait_table[hash_long(val, zone->wait_table_bits)]; +} +EXPORT_SYMBOL(bit_waitqueue); -- cgit v1.2.3 From 493267b6ec40026be65e3564fd24b879be3c06d1 Mon Sep 17 00:00:00 2001 From: William Lee Irwin III Date: Mon, 18 Oct 2004 18:00:17 -0700 Subject: [PATCH] eliminate inode waitqueue hashtable Eliminate the inode waitqueue hashtable using bit_waitqueue() via wait_on_bit() and wake_up_bit() to locate the waitqueue head associated with a bit. Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/fs-writeback.c | 20 +++++++++----- fs/inode.c | 69 ++++++++++++++--------------------------------- include/linux/fs.h | 3 ++- include/linux/writeback.h | 6 ++--- kernel/wait.c | 1 + 5 files changed, 40 insertions(+), 59 deletions(-) diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c index da522d511f2d..d70f1fc9b71f 100644 --- a/fs/fs-writeback.c +++ b/fs/fs-writeback.c @@ -244,6 +244,8 @@ static int __writeback_single_inode(struct inode *inode, struct writeback_control *wbc) { + wait_queue_head_t *wqh; + if ((wbc->sync_mode != WB_SYNC_ALL) && (inode->i_state & I_LOCK)) { list_move(&inode->i_list, &inode->i_sb->s_dirty); return 0; @@ -252,12 +254,18 @@ __writeback_single_inode(struct inode *inode, /* * It's a data-integrity sync. We must wait. */ - while (inode->i_state & I_LOCK) { - __iget(inode); - spin_unlock(&inode_lock); - __wait_on_inode(inode); - iput(inode); - spin_lock(&inode_lock); + if (inode->i_state & I_LOCK) { + DEFINE_WAIT_BIT(wq, &inode->i_state, __I_LOCK); + + wqh = bit_waitqueue(&inode->i_state, __I_LOCK); + do { + __iget(inode); + spin_unlock(&inode_lock); + __wait_on_bit(wqh, &wq, &inode->i_state, __I_LOCK, + inode_wait, TASK_UNINTERRUPTIBLE); + iput(inode); + spin_lock(&inode_lock); + } while (inode->i_state & I_LOCK); } return __sync_single_inode(inode, wbc); } diff --git a/fs/inode.c b/fs/inode.c index 8cd74200bdff..1fa7de5e8f84 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -1264,37 +1264,10 @@ void remove_dquot_ref(struct super_block *sb, int type, struct list_head *tofree #endif -/* - * Hashed waitqueues for wait_on_inode(). The table is pretty small - the - * kernel doesn't lock many inodes at the same time. - */ -#define I_WAIT_TABLE_ORDER 3 -static struct i_wait_queue_head { - wait_queue_head_t wqh; -} ____cacheline_aligned_in_smp i_wait_queue_heads[1<i_state & I_LOCK) { - schedule(); - goto repeat; - } - remove_wait_queue(wq, &wait); - __set_current_state(TASK_RUNNING); + schedule(); + return 0; } /* @@ -1303,36 +1276,39 @@ repeat: * that it isn't found. This is because iget will immediately call * ->read_inode, and we want to be sure that evidence of the deletion is found * by ->read_inode. - * - * This call might return early if an inode which shares the waitq is woken up. - * This is most easily handled by the caller which will loop around again - * looking for the inode. - * * This is called with inode_lock held. */ static void __wait_on_freeing_inode(struct inode *inode) { - DECLARE_WAITQUEUE(wait, current); - wait_queue_head_t *wq = i_waitq_head(inode); + wait_queue_head_t *wq; + DEFINE_WAIT_BIT(wait, &inode->i_state, __I_LOCK); - add_wait_queue(wq, &wait); - set_current_state(TASK_UNINTERRUPTIBLE); + /* + * I_FREEING and I_CLEAR are cleared in process context under + * inode_lock, so we have to give the tasks who would clear them + * a chance to run and acquire inode_lock. + */ + if (!(inode->i_state & I_LOCK)) { + spin_unlock(&inode_lock); + yield(); + spin_lock(&inode_lock); + return; + } + wq = bit_waitqueue(&inode->i_state, __I_LOCK); + prepare_to_wait(wq, &wait.wait, TASK_UNINTERRUPTIBLE); spin_unlock(&inode_lock); schedule(); - remove_wait_queue(wq, &wait); + finish_wait(wq, &wait.wait); spin_lock(&inode_lock); } void wake_up_inode(struct inode *inode) { - wait_queue_head_t *wq = i_waitq_head(inode); - /* * Prevent speculative execution through spin_unlock(&inode_lock); */ smp_mb(); - if (waitqueue_active(wq)) - wake_up_all(wq); + wake_up_bit(&inode->i_state, __I_LOCK); } static __initdata unsigned long ihash_entries; @@ -1367,11 +1343,6 @@ void __init inode_init_early(void) void __init inode_init(unsigned long mempages) { - int i; - - for (i = 0; i < ARRAY_SIZE(i_wait_queue_heads); i++) - init_waitqueue_head(&i_wait_queue_heads[i].wqh); - /* inode slab cache */ inode_cachep = kmem_cache_create("inode_cache", sizeof(struct inode), 0, SLAB_PANIC, init_once, NULL); diff --git a/include/linux/fs.h b/include/linux/fs.h index 4f6fe6b575a8..2a13baa4250b 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -981,7 +981,8 @@ struct super_operations { #define I_DIRTY_SYNC 1 /* Not dirty enough for O_DATASYNC */ #define I_DIRTY_DATASYNC 2 /* Data-related inode changes pending */ #define I_DIRTY_PAGES 4 /* Data-related inode changes pending */ -#define I_LOCK 8 +#define __I_LOCK 3 +#define I_LOCK (1 << __I_LOCK) #define I_FREEING 16 #define I_CLEAR 32 #define I_NEW 64 diff --git a/include/linux/writeback.h b/include/linux/writeback.h index 7c165c334be5..1c9994fe2acc 100644 --- a/include/linux/writeback.h +++ b/include/linux/writeback.h @@ -68,7 +68,7 @@ struct writeback_control { */ void writeback_inodes(struct writeback_control *wbc); void wake_up_inode(struct inode *inode); -void __wait_on_inode(struct inode * inode); +int inode_wait(void *); void sync_inodes_sb(struct super_block *, int wait); void sync_inodes(int wait); @@ -76,8 +76,8 @@ void sync_inodes(int wait); static inline void wait_on_inode(struct inode *inode) { might_sleep(); - if (inode->i_state & I_LOCK) - __wait_on_inode(inode); + wait_on_bit(&inode->i_state, __I_LOCK, inode_wait, + TASK_UNINTERRUPTIBLE); } /* diff --git a/kernel/wait.c b/kernel/wait.c index e87ae721643c..4750562fd56e 100644 --- a/kernel/wait.c +++ b/kernel/wait.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include -- cgit v1.2.3 From bc341c61fe34e873446bea41beff938f5bee44f2 Mon Sep 17 00:00:00 2001 From: William Lee Irwin III Date: Mon, 18 Oct 2004 18:00:29 -0700 Subject: [PATCH] move wait ops' contention case completely out of line Move the slow paths of wait_on_bit() and wait_on_bit_lock() out of line. Also uninline wake_up_bit() to reduce the number of callsites generated, and adjust loop startup in __wait_on_bit_lock() to properly reflect its usage in the contention case. Incremental atop the fastcall and wait_on_bit_lock()/test_and_set_bit() fixes. Successfully tested on x86-64. Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- include/linux/wait.h | 36 +++++++----------------------------- kernel/wait.c | 40 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 31 deletions(-) diff --git a/include/linux/wait.h b/include/linux/wait.h index f58a313cc5b3..c7dd35b371e5 100644 --- a/include/linux/wait.h +++ b/include/linux/wait.h @@ -142,23 +142,11 @@ extern void FASTCALL(__wake_up_sync(wait_queue_head_t *q, unsigned int mode, int void FASTCALL(__wake_up_bit(wait_queue_head_t *, void *, int)); int FASTCALL(__wait_on_bit(wait_queue_head_t *, struct wait_bit_queue *, void *, int, int (*)(void *), unsigned)); int FASTCALL(__wait_on_bit_lock(wait_queue_head_t *, struct wait_bit_queue *, void *, int, int (*)(void *), unsigned)); +void FASTCALL(wake_up_bit(void *, int)); +int FASTCALL(out_of_line_wait_on_bit(void *, int, int (*)(void *), unsigned)); +int FASTCALL(out_of_line_wait_on_bit_lock(void *, int, int (*)(void *), unsigned)); wait_queue_head_t *FASTCALL(bit_waitqueue(void *, int)); -/** - * wake_up_bit - wake up a waiter on a bit - * @word: the word being waited on, a kernel virtual address - * @bit: the bit of the word being waited on - * - * There is a standard hashed waitqueue table for generic use. This - * is the part of the hashtable's accessor API that wakes up waiters - * on a bit. For instance, if one were to have waiters on a bitflag, - * one would call wake_up_bit() after clearing the bit. - */ -static inline void wake_up_bit(void *word, int bit) -{ - __wake_up_bit(bit_waitqueue(word, bit), word, bit); -} - #define wake_up(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1, NULL) #define wake_up_nr(x, nr) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, nr, NULL) #define wake_up_all(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 0, NULL) @@ -379,14 +367,9 @@ int wake_bit_function(wait_queue_t *wait, unsigned mode, int sync, void *key); static inline int wait_on_bit(void *word, int bit, int (*action)(void *), unsigned mode) { - DEFINE_WAIT_BIT(q, word, bit); - wait_queue_head_t *wqh; - - if (!test_and_set_bit(bit, word)) + if (!test_bit(bit, word)) return 0; - - wqh = bit_waitqueue(word, bit); - return __wait_on_bit(wqh, &q, word, bit, action, mode); + return out_of_line_wait_on_bit(word, bit, action, mode); } /** @@ -408,14 +391,9 @@ static inline int wait_on_bit(void *word, int bit, static inline int wait_on_bit_lock(void *word, int bit, int (*action)(void *), unsigned mode) { - DEFINE_WAIT_BIT(q, word, bit); - wait_queue_head_t *wqh; - - if (!test_bit(bit, word)) + if (!test_and_set_bit(bit, word)) return 0; - - wqh = bit_waitqueue(word, bit); - return __wait_on_bit_lock(wqh, &q, word, bit, action, mode); + return out_of_line_wait_on_bit_lock(word, bit, action, mode); } #endif /* __KERNEL__ */ diff --git a/kernel/wait.c b/kernel/wait.c index 4750562fd56e..ebb3da44dc12 100644 --- a/kernel/wait.c +++ b/kernel/wait.c @@ -164,24 +164,44 @@ __wait_on_bit(wait_queue_head_t *wq, struct wait_bit_queue *q, } EXPORT_SYMBOL(__wait_on_bit); +int __sched fastcall out_of_line_wait_on_bit(void *word, int bit, + int (*action)(void *), unsigned mode) +{ + wait_queue_head_t *wq = bit_waitqueue(word, bit); + DEFINE_WAIT_BIT(wait, word, bit); + + return __wait_on_bit(wq, &wait, word, bit, action, mode); +} +EXPORT_SYMBOL(out_of_line_wait_on_bit); + int __sched fastcall __wait_on_bit_lock(wait_queue_head_t *wq, struct wait_bit_queue *q, void *word, int bit, int (*action)(void *), unsigned mode) { int ret = 0; - while (test_and_set_bit(bit, word)) { + do { prepare_to_wait_exclusive(wq, &q->wait, mode); if (test_bit(bit, word)) { if ((ret = (*action)(word))) break; } - } + } while (test_and_set_bit(bit, word)); finish_wait(wq, &q->wait); return ret; } EXPORT_SYMBOL(__wait_on_bit_lock); +int __sched fastcall out_of_line_wait_on_bit_lock(void *word, int bit, + int (*action)(void *), unsigned mode) +{ + wait_queue_head_t *wq = bit_waitqueue(word, bit); + DEFINE_WAIT_BIT(wait, word, bit); + + return __wait_on_bit_lock(wq, &wait, word, bit, action, mode); +} +EXPORT_SYMBOL(out_of_line_wait_on_bit_lock); + void fastcall __wake_up_bit(wait_queue_head_t *wq, void *word, int bit) { struct wait_bit_key key = __WAIT_BIT_KEY_INITIALIZER(word, bit); @@ -190,6 +210,22 @@ void fastcall __wake_up_bit(wait_queue_head_t *wq, void *word, int bit) } EXPORT_SYMBOL(__wake_up_bit); +/** + * wake_up_bit - wake up a waiter on a bit + * @word: the word being waited on, a kernel virtual address + * @bit: the bit of the word being waited on + * + * There is a standard hashed waitqueue table for generic use. This + * is the part of the hashtable's accessor API that wakes up waiters + * on a bit. For instance, if one were to have waiters on a bitflag, + * one would call wake_up_bit() after clearing the bit. + */ +void fastcall wake_up_bit(void *word, int bit) +{ + __wake_up_bit(bit_waitqueue(word, bit), word, bit); +} +EXPORT_SYMBOL(wake_up_bit); + fastcall wait_queue_head_t *bit_waitqueue(void *word, int bit) { const int shift = BITS_PER_LONG == 32 ? 5 : 6; -- cgit v1.2.3 From 9659cc899afad76f8d45e91e4ed244d65d95cf61 Mon Sep 17 00:00:00 2001 From: William Lee Irwin III Date: Mon, 18 Oct 2004 18:00:40 -0700 Subject: [PATCH] reduce number of parameters to __wait_on_bit() and __wait_on_bit_lock() Some of the parameters to __wait_on_bit() and __wait_on_bit_lock() are redundant, as the wait_bit_queue parameter holds the flags word and the bit number. This patch updates __wait_on_bit() and __wait_on_bit_lock() to fetch that information from the wait_bit_queue passed to them and so reduce the number of parameters so that -mregparm may be more effective. Incremental atop the complete out-of-lining of the contention cases and the fastcall and wait_on_bit_lock()/test_and_set_bit() fixes. Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/fs-writeback.c | 4 ++-- include/linux/wait.h | 4 ++-- kernel/wait.c | 22 ++++++++++++---------- mm/filemap.c | 8 ++++---- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c index d70f1fc9b71f..969e9b0e0afc 100644 --- a/fs/fs-writeback.c +++ b/fs/fs-writeback.c @@ -261,8 +261,8 @@ __writeback_single_inode(struct inode *inode, do { __iget(inode); spin_unlock(&inode_lock); - __wait_on_bit(wqh, &wq, &inode->i_state, __I_LOCK, - inode_wait, TASK_UNINTERRUPTIBLE); + __wait_on_bit(wqh, &wq, inode_wait, + TASK_UNINTERRUPTIBLE); iput(inode); spin_lock(&inode_lock); } while (inode->i_state & I_LOCK); diff --git a/include/linux/wait.h b/include/linux/wait.h index c7dd35b371e5..146cceee0221 100644 --- a/include/linux/wait.h +++ b/include/linux/wait.h @@ -140,8 +140,8 @@ void FASTCALL(__wake_up(wait_queue_head_t *q, unsigned int mode, int nr, void *k extern void FASTCALL(__wake_up_locked(wait_queue_head_t *q, unsigned int mode)); extern void FASTCALL(__wake_up_sync(wait_queue_head_t *q, unsigned int mode, int nr)); void FASTCALL(__wake_up_bit(wait_queue_head_t *, void *, int)); -int FASTCALL(__wait_on_bit(wait_queue_head_t *, struct wait_bit_queue *, void *, int, int (*)(void *), unsigned)); -int FASTCALL(__wait_on_bit_lock(wait_queue_head_t *, struct wait_bit_queue *, void *, int, int (*)(void *), unsigned)); +int FASTCALL(__wait_on_bit(wait_queue_head_t *, struct wait_bit_queue *, int (*)(void *), unsigned)); +int FASTCALL(__wait_on_bit_lock(wait_queue_head_t *, struct wait_bit_queue *, int (*)(void *), unsigned)); void FASTCALL(wake_up_bit(void *, int)); int FASTCALL(out_of_line_wait_on_bit(void *, int, int (*)(void *), unsigned)); int FASTCALL(out_of_line_wait_on_bit_lock(void *, int, int (*)(void *), unsigned)); diff --git a/kernel/wait.c b/kernel/wait.c index ebb3da44dc12..1ca53daecd91 100644 --- a/kernel/wait.c +++ b/kernel/wait.c @@ -152,13 +152,15 @@ EXPORT_SYMBOL(wake_bit_function); */ int __sched fastcall __wait_on_bit(wait_queue_head_t *wq, struct wait_bit_queue *q, - void *word, int bit, int (*action)(void *), unsigned mode) + int (*action)(void *), unsigned mode) { int ret = 0; - prepare_to_wait(wq, &q->wait, mode); - if (test_bit(bit, word)) - ret = (*action)(word); + do { + prepare_to_wait(wq, &q->wait, mode); + if (test_bit(q->key.bit_nr, q->key.flags)) + ret = (*action)(q->key.flags); + } while (test_bit(q->key.bit_nr, q->key.flags) && !ret); finish_wait(wq, &q->wait); return ret; } @@ -170,23 +172,23 @@ int __sched fastcall out_of_line_wait_on_bit(void *word, int bit, wait_queue_head_t *wq = bit_waitqueue(word, bit); DEFINE_WAIT_BIT(wait, word, bit); - return __wait_on_bit(wq, &wait, word, bit, action, mode); + return __wait_on_bit(wq, &wait, action, mode); } EXPORT_SYMBOL(out_of_line_wait_on_bit); int __sched fastcall __wait_on_bit_lock(wait_queue_head_t *wq, struct wait_bit_queue *q, - void *word, int bit, int (*action)(void *), unsigned mode) + int (*action)(void *), unsigned mode) { int ret = 0; do { prepare_to_wait_exclusive(wq, &q->wait, mode); - if (test_bit(bit, word)) { - if ((ret = (*action)(word))) + if (test_bit(q->key.bit_nr, q->key.flags)) { + if ((ret = (*action)(q->key.flags))) break; } - } while (test_and_set_bit(bit, word)); + } while (test_and_set_bit(q->key.bit_nr, q->key.flags)); finish_wait(wq, &q->wait); return ret; } @@ -198,7 +200,7 @@ int __sched fastcall out_of_line_wait_on_bit_lock(void *word, int bit, wait_queue_head_t *wq = bit_waitqueue(word, bit); DEFINE_WAIT_BIT(wait, word, bit); - return __wait_on_bit_lock(wq, &wait, word, bit, action, mode); + return __wait_on_bit_lock(wq, &wait, action, mode); } EXPORT_SYMBOL(out_of_line_wait_on_bit_lock); diff --git a/mm/filemap.c b/mm/filemap.c index 14159485b57c..382bd020a33f 100644 --- a/mm/filemap.c +++ b/mm/filemap.c @@ -381,8 +381,8 @@ void fastcall wait_on_page_bit(struct page *page, int bit_nr) DEFINE_WAIT_BIT(wait, &page->flags, bit_nr); if (test_bit(bit_nr, &page->flags)) - __wait_on_bit(page_waitqueue(page), &wait, wait.key.flags, - bit_nr, sync_page, TASK_UNINTERRUPTIBLE); + __wait_on_bit(page_waitqueue(page), &wait, sync_page, + TASK_UNINTERRUPTIBLE); } EXPORT_SYMBOL(wait_on_page_bit); @@ -440,8 +440,8 @@ void fastcall __lock_page(struct page *page) { DEFINE_WAIT_BIT(wait, &page->flags, PG_locked); - __wait_on_bit_lock(page_waitqueue(page), &wait, wait.key.flags, - PG_locked, sync_page, TASK_UNINTERRUPTIBLE); + __wait_on_bit_lock(page_waitqueue(page), &wait, sync_page, + TASK_UNINTERRUPTIBLE); } EXPORT_SYMBOL(__lock_page); -- cgit v1.2.3 From a8589849ae101ada752982b34ab4a6d8f81bfcb3 Mon Sep 17 00:00:00 2001 From: William Lee Irwin III Date: Mon, 18 Oct 2004 18:00:51 -0700 Subject: [PATCH] document wake_up_bit()'s requirement for preceding memory barriers Document the requirement to use a memory barrier prior to wake_up_bit(). Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- kernel/wait.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/kernel/wait.c b/kernel/wait.c index 1ca53daecd91..791681cfea98 100644 --- a/kernel/wait.c +++ b/kernel/wait.c @@ -221,6 +221,13 @@ EXPORT_SYMBOL(__wake_up_bit); * is the part of the hashtable's accessor API that wakes up waiters * on a bit. For instance, if one were to have waiters on a bitflag, * one would call wake_up_bit() after clearing the bit. + * + * In order for this to function properly, as it uses waitqueue_active() + * internally, some kind of memory barrier must be done prior to calling + * this. Typically, this will be smp_mb__after_clear_bit(), but in some + * cases where bitflags are manipulated non-atomically under a lock, one + * may need to use a less regular barrier, such fs/inode.c's smp_mb(), + * because spin_unlock() does not guarantee a memory barrier. */ void fastcall wake_up_bit(void *word, int bit) { -- cgit v1.2.3 From 91cd0c2bdb62901f3a8fcac3584b392f3c8115b0 Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Mon, 18 Oct 2004 18:01:03 -0700 Subject: [PATCH] jbd wakeup fix Processes can sleep in do_get_write_access(), waiting for buffers to be removed from the BJ_Shadow state. We did this by doing a wake_up_buffer() in the commit path and sleeping on the buffer in do_get_write_access(). With the filtered bit-level wakeup code this doesn't work properly any more - the wake_up_buffer() accidentally wakes up tasks which are sleeping in lock_buffer() as well. Those tasks now implicitly assume that the buffer came unlocked. Net effect: Bogus I/O errors when reading journal blocks, because the buffer isn't up to date yet. Hence the recently spate of journal_bmap() failure reports. The patch creates a new jbd-private BH flag purely for this wakeup function. So a wake_up_bit(..., BH_Unshadow) doesn't wake up someone who is waiting for a wake_up_bit(BH_Lock). JBD was the only user of wake_up_buffer(), so remove it altogether. Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/buffer.c | 11 ++--------- fs/jbd/commit.c | 2 +- fs/jbd/transaction.c | 7 ++++--- include/linux/buffer_head.h | 1 - include/linux/jbd.h | 1 + 5 files changed, 8 insertions(+), 14 deletions(-) diff --git a/fs/buffer.c b/fs/buffer.c index a8cfc265ec64..2a75b3f9efe4 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -52,13 +52,6 @@ init_buffer(struct buffer_head *bh, bh_end_io_t *handler, void *private) bh->b_private = private; } -void wake_up_buffer(struct buffer_head *bh) -{ - smp_mb(); - wake_up_bit(&bh->b_state, BH_Lock); -} -EXPORT_SYMBOL(wake_up_buffer); - static int sync_buffer(void *word) { struct block_device *bd; @@ -83,8 +76,8 @@ EXPORT_SYMBOL(__lock_buffer); void fastcall unlock_buffer(struct buffer_head *bh) { clear_buffer_locked(bh); - smp_mb__after_clear_bit(); - wake_up_buffer(bh); + smp_mb(); + wake_up_bit(&bh->b_state, BH_Lock); } /* diff --git a/fs/jbd/commit.c b/fs/jbd/commit.c index f8a1dea56611..b4d6654ef7f2 100644 --- a/fs/jbd/commit.c +++ b/fs/jbd/commit.c @@ -579,7 +579,7 @@ wait_for_iobuf: journal_file_buffer(jh, commit_transaction, BJ_Forget); /* Wake up any transactions which were waiting for this IO to complete */ - wake_up_buffer(bh); + wake_up_bit(&bh->b_state, BH_Unshadow); JBUFFER_TRACE(jh, "brelse shadowed buffer"); __brelse(bh); } diff --git a/fs/jbd/transaction.c b/fs/jbd/transaction.c index 304165faa19d..a168757d26af 100644 --- a/fs/jbd/transaction.c +++ b/fs/jbd/transaction.c @@ -633,9 +633,10 @@ repeat: * disk then we cannot do copy-out here. */ if (jh->b_jlist == BJ_Shadow) { - DEFINE_WAIT_BIT(wait, &bh->b_state, BH_Lock); - wait_queue_head_t *wqh - = bit_waitqueue(&bh->b_state, BH_Lock); + DEFINE_WAIT_BIT(wait, &bh->b_state, BH_Unshadow); + wait_queue_head_t *wqh; + + wqh = bit_waitqueue(&bh->b_state, BH_Unshadow); JBUFFER_TRACE(jh, "on shadow: sleep"); jbd_unlock_bh_state(bh); diff --git a/include/linux/buffer_head.h b/include/linux/buffer_head.h index 367a8a313506..47fb6a02d630 100644 --- a/include/linux/buffer_head.h +++ b/include/linux/buffer_head.h @@ -155,7 +155,6 @@ void invalidate_bdev(struct block_device *, int); int sync_blockdev(struct block_device *bdev); void __wait_on_buffer(struct buffer_head *); wait_queue_head_t *bh_waitq_head(struct buffer_head *bh); -void wake_up_buffer(struct buffer_head *bh); int fsync_bdev(struct block_device *); struct super_block *freeze_bdev(struct block_device *); void thaw_bdev(struct block_device *, struct super_block *); diff --git a/include/linux/jbd.h b/include/linux/jbd.h index e65b90f1962c..dfdd307872bb 100644 --- a/include/linux/jbd.h +++ b/include/linux/jbd.h @@ -299,6 +299,7 @@ enum jbd_state_bits { BH_JBDDirty, /* Is dirty but journaled */ BH_State, /* Pins most journal_head state */ BH_JournalHead, /* Pins bh->b_private and jh->b_bh */ + BH_Unshadow, /* Dummy bit, for BJ_Shadow wakeup filtering */ }; BUFFER_FNS(JBD, jbd) -- cgit v1.2.3 From 3d3d87471e1f45e3951c4860659cc4495cdafe6d Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Mon, 18 Oct 2004 18:01:16 -0700 Subject: [PATCH] unreachable code in ext3_direct_IO() davej points out that in this code local variable `ret' is already known to be positive non-zero, so this test is meaningless. Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/ext3/inode.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/fs/ext3/inode.c b/fs/ext3/inode.c index 0e48f620d9e8..cf7225964a33 100644 --- a/fs/ext3/inode.c +++ b/fs/ext3/inode.c @@ -1596,9 +1596,14 @@ out_stop: if (end > inode->i_size) { ei->i_disksize = end; i_size_write(inode, end); - err = ext3_mark_inode_dirty(handle, inode); - if (!ret) - ret = err; + /* + * We're going to return a positive `ret' + * here due to non-zero-length I/O, so there's + * no way of reporting error returns from + * ext3_mark_inode_dirty() to userspace. So + * ignore it. + */ + ext3_mark_inode_dirty(handle, inode); } } err = ext3_journal_stop(handle); -- cgit v1.2.3 From df02202cfb0d7df1c28225c7da0c3deb3698a730 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Mon, 18 Oct 2004 18:01:28 -0700 Subject: [PATCH] switchable and modular io schedulers This patch modularizes the io schedulers completely, allowing them to be modular. Additionally it enables online switching of io schedulers. See also http://lwn.net/Articles/102593/ . There's a scheduler file in the sysfs directory for the block device queue: axboe@router:/sys/block/hda/queue> ls iosched max_sectors_kb read_ahead_kb max_hw_sectors_kb nr_requests scheduler If you list the contents of the file, it will show available schedulers and the active one: axboe@router:/sys/block/hda/queue> cat scheduler [cfq] Lets load a few more. router:/sys/block/hda/queue # modprobe deadline-iosched router:/sys/block/hda/queue # modprobe as-iosched router:/sys/block/hda/queue # cat scheduler [cfq] deadline anticipatory Changing is done with router:/sys/block/hda/queue # echo deadline > scheduler router:/sys/block/hda/queue # cat scheduler cfq [deadline] anticipatory deadline is now the new active io scheduler for hda. Signed-off-by: Jens Axboe Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/block/Kconfig.iosched | 8 +- drivers/block/as-iosched.c | 111 ++++++++------ drivers/block/cfq-iosched.c | 113 +++++++++----- drivers/block/deadline-iosched.c | 90 ++++++----- drivers/block/elevator.c | 316 ++++++++++++++++++++++++++++++++------- drivers/block/ll_rw_blk.c | 140 ++++++++++------- drivers/block/noop-iosched.c | 33 +++- drivers/s390/block/dasd.c | 4 +- drivers/s390/char/tape_block.c | 4 +- include/linux/blkdev.h | 10 +- include/linux/elevator.h | 55 ++++--- 11 files changed, 622 insertions(+), 262 deletions(-) diff --git a/drivers/block/Kconfig.iosched b/drivers/block/Kconfig.iosched index d938c5fd130b..e0ba6c93717e 100644 --- a/drivers/block/Kconfig.iosched +++ b/drivers/block/Kconfig.iosched @@ -1,5 +1,5 @@ config IOSCHED_NOOP - bool "No-op I/O scheduler" if EMBEDDED + bool default y ---help--- The no-op I/O scheduler is a minimal scheduler that does basic merging @@ -9,7 +9,7 @@ config IOSCHED_NOOP the kernel. config IOSCHED_AS - bool "Anticipatory I/O scheduler" if EMBEDDED + tristate "Anticipatory I/O scheduler" default y ---help--- The anticipatory I/O scheduler is the default disk scheduler. It is @@ -18,7 +18,7 @@ config IOSCHED_AS slower in some cases especially some database loads. config IOSCHED_DEADLINE - bool "Deadline I/O scheduler" if EMBEDDED + tristate "Deadline I/O scheduler" default y ---help--- The deadline I/O scheduler is simple and compact, and is often as @@ -28,7 +28,7 @@ config IOSCHED_DEADLINE anticipatory I/O scheduler and so is a good choice. config IOSCHED_CFQ - bool "CFQ I/O scheduler" if EMBEDDED + tristate "CFQ I/O scheduler" default y ---help--- The CFQ I/O scheduler tries to distribute bandwidth equally diff --git a/drivers/block/as-iosched.c b/drivers/block/as-iosched.c index 0ef6a665d93e..bb3e9b5bab3c 100644 --- a/drivers/block/as-iosched.c +++ b/drivers/block/as-iosched.c @@ -614,7 +614,7 @@ static void as_antic_stop(struct as_data *ad) static void as_antic_timeout(unsigned long data) { struct request_queue *q = (struct request_queue *)data; - struct as_data *ad = q->elevator.elevator_data; + struct as_data *ad = q->elevator->elevator_data; unsigned long flags; spin_lock_irqsave(q->queue_lock, flags); @@ -945,7 +945,7 @@ static void update_write_batch(struct as_data *ad) */ static void as_completed_request(request_queue_t *q, struct request *rq) { - struct as_data *ad = q->elevator.elevator_data; + struct as_data *ad = q->elevator->elevator_data; struct as_rq *arq = RQ_DATA(rq); WARN_ON(!list_empty(&rq->queuelist)); @@ -1030,7 +1030,7 @@ static void as_remove_queued_request(request_queue_t *q, struct request *rq) { struct as_rq *arq = RQ_DATA(rq); const int data_dir = arq->is_sync; - struct as_data *ad = q->elevator.elevator_data; + struct as_data *ad = q->elevator->elevator_data; WARN_ON(arq->state != AS_RQ_QUEUED); @@ -1361,7 +1361,7 @@ fifo_expired: static struct request *as_next_request(request_queue_t *q) { - struct as_data *ad = q->elevator.elevator_data; + struct as_data *ad = q->elevator->elevator_data; struct request *rq = NULL; /* @@ -1469,7 +1469,7 @@ static void as_add_request(struct as_data *ad, struct as_rq *arq) */ static void as_requeue_request(request_queue_t *q, struct request *rq) { - struct as_data *ad = q->elevator.elevator_data; + struct as_data *ad = q->elevator->elevator_data; struct as_rq *arq = RQ_DATA(rq); if (arq) { @@ -1509,7 +1509,7 @@ static void as_account_queued_request(struct as_data *ad, struct request *rq) static void as_insert_request(request_queue_t *q, struct request *rq, int where) { - struct as_data *ad = q->elevator.elevator_data; + struct as_data *ad = q->elevator->elevator_data; struct as_rq *arq = RQ_DATA(rq); if (arq) { @@ -1562,7 +1562,7 @@ as_insert_request(request_queue_t *q, struct request *rq, int where) */ static int as_queue_empty(request_queue_t *q) { - struct as_data *ad = q->elevator.elevator_data; + struct as_data *ad = q->elevator->elevator_data; if (!list_empty(&ad->fifo_list[REQ_ASYNC]) || !list_empty(&ad->fifo_list[REQ_SYNC]) @@ -1601,7 +1601,7 @@ as_latter_request(request_queue_t *q, struct request *rq) static int as_merge(request_queue_t *q, struct request **req, struct bio *bio) { - struct as_data *ad = q->elevator.elevator_data; + struct as_data *ad = q->elevator->elevator_data; sector_t rb_key = bio->bi_sector + bio_sectors(bio); struct request *__rq; int ret; @@ -1656,7 +1656,7 @@ out_insert: static void as_merged_request(request_queue_t *q, struct request *req) { - struct as_data *ad = q->elevator.elevator_data; + struct as_data *ad = q->elevator->elevator_data; struct as_rq *arq = RQ_DATA(req); /* @@ -1701,7 +1701,7 @@ static void as_merged_requests(request_queue_t *q, struct request *req, struct request *next) { - struct as_data *ad = q->elevator.elevator_data; + struct as_data *ad = q->elevator->elevator_data; struct as_rq *arq = RQ_DATA(req); struct as_rq *anext = RQ_DATA(next); @@ -1788,7 +1788,7 @@ static void as_work_handler(void *data) static void as_put_request(request_queue_t *q, struct request *rq) { - struct as_data *ad = q->elevator.elevator_data; + struct as_data *ad = q->elevator->elevator_data; struct as_rq *arq = RQ_DATA(rq); if (!arq) { @@ -1807,7 +1807,7 @@ static void as_put_request(request_queue_t *q, struct request *rq) static int as_set_request(request_queue_t *q, struct request *rq, int gfp_mask) { - struct as_data *ad = q->elevator.elevator_data; + struct as_data *ad = q->elevator->elevator_data; struct as_rq *arq = mempool_alloc(ad->arq_pool, gfp_mask); if (arq) { @@ -1829,7 +1829,7 @@ static int as_set_request(request_queue_t *q, struct request *rq, int gfp_mask) static int as_may_queue(request_queue_t *q, int rw) { int ret = 0; - struct as_data *ad = q->elevator.elevator_data; + struct as_data *ad = q->elevator->elevator_data; struct io_context *ioc; if (ad->antic_status == ANTIC_WAIT_REQ || ad->antic_status == ANTIC_WAIT_NEXT) { @@ -1842,7 +1842,7 @@ static int as_may_queue(request_queue_t *q, int rw) return ret; } -static void as_exit(request_queue_t *q, elevator_t *e) +static void as_exit_queue(elevator_t *e) { struct as_data *ad = e->elevator_data; @@ -1862,7 +1862,7 @@ static void as_exit(request_queue_t *q, elevator_t *e) * initialize elevator private data (as_data), and alloc a arq for * each request on the free lists */ -static int as_init(request_queue_t *q, elevator_t *e) +static int as_init_queue(request_queue_t *q, elevator_t *e) { struct as_data *ad; int i; @@ -2070,39 +2070,64 @@ static struct kobj_type as_ktype = { .default_attrs = default_attrs, }; -static int __init as_slab_setup(void) +static struct elevator_type iosched_as = { + .ops = { + .elevator_merge_fn = as_merge, + .elevator_merged_fn = as_merged_request, + .elevator_merge_req_fn = as_merged_requests, + .elevator_next_req_fn = as_next_request, + .elevator_add_req_fn = as_insert_request, + .elevator_remove_req_fn = as_remove_request, + .elevator_requeue_req_fn = as_requeue_request, + .elevator_queue_empty_fn = as_queue_empty, + .elevator_completed_req_fn = as_completed_request, + .elevator_former_req_fn = as_former_request, + .elevator_latter_req_fn = as_latter_request, + .elevator_set_req_fn = as_set_request, + .elevator_put_req_fn = as_put_request, + .elevator_may_queue_fn = as_may_queue, + .elevator_init_fn = as_init_queue, + .elevator_exit_fn = as_exit_queue, + }, + + .elevator_ktype = &as_ktype, + .elevator_name = "anticipatory", + .elevator_owner = THIS_MODULE, +}; + +int as_init(void) { + int ret; + arq_pool = kmem_cache_create("as_arq", sizeof(struct as_rq), 0, 0, NULL, NULL); - if (!arq_pool) - panic("as: can't init slab pool\n"); + return -ENOMEM; - return 0; + ret = elv_register(&iosched_as); + if (!ret) { + /* + * don't allow AS to get unregistered, since we would have + * to browse all tasks in the system and release their + * as_io_context first + */ + __module_get(THIS_MODULE); + return 0; + } + + kmem_cache_destroy(arq_pool); + return ret; } -subsys_initcall(as_slab_setup); - -elevator_t iosched_as = { - .elevator_merge_fn = as_merge, - .elevator_merged_fn = as_merged_request, - .elevator_merge_req_fn = as_merged_requests, - .elevator_next_req_fn = as_next_request, - .elevator_add_req_fn = as_insert_request, - .elevator_remove_req_fn = as_remove_request, - .elevator_requeue_req_fn = as_requeue_request, - .elevator_queue_empty_fn = as_queue_empty, - .elevator_completed_req_fn = as_completed_request, - .elevator_former_req_fn = as_former_request, - .elevator_latter_req_fn = as_latter_request, - .elevator_set_req_fn = as_set_request, - .elevator_put_req_fn = as_put_request, - .elevator_may_queue_fn = as_may_queue, - .elevator_init_fn = as_init, - .elevator_exit_fn = as_exit, - - .elevator_ktype = &as_ktype, - .elevator_name = "anticipatory", -}; +void as_exit(void) +{ + kmem_cache_destroy(arq_pool); + elv_unregister(&iosched_as); +} + +module_init(as_init); +module_exit(as_exit); -EXPORT_SYMBOL(iosched_as); +MODULE_AUTHOR("Nick Piggin"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("anticipatory IO scheduler"); diff --git a/drivers/block/cfq-iosched.c b/drivers/block/cfq-iosched.c index 068f4eae0b5c..6a424dc65823 100644 --- a/drivers/block/cfq-iosched.c +++ b/drivers/block/cfq-iosched.c @@ -246,7 +246,7 @@ out: static void cfq_remove_request(request_queue_t *q, struct request *rq) { - struct cfq_data *cfqd = q->elevator.elevator_data; + struct cfq_data *cfqd = q->elevator->elevator_data; struct cfq_rq *crq = RQ_DATA(rq); if (crq) { @@ -267,7 +267,7 @@ static void cfq_remove_request(request_queue_t *q, struct request *rq) static int cfq_merge(request_queue_t *q, struct request **req, struct bio *bio) { - struct cfq_data *cfqd = q->elevator.elevator_data; + struct cfq_data *cfqd = q->elevator->elevator_data; struct request *__rq; int ret; @@ -305,7 +305,7 @@ out_insert: static void cfq_merged_request(request_queue_t *q, struct request *req) { - struct cfq_data *cfqd = q->elevator.elevator_data; + struct cfq_data *cfqd = q->elevator->elevator_data; struct cfq_rq *crq = RQ_DATA(req); cfq_del_crq_hash(crq); @@ -404,7 +404,7 @@ restart: static struct request *cfq_next_request(request_queue_t *q) { - struct cfq_data *cfqd = q->elevator.elevator_data; + struct cfq_data *cfqd = q->elevator->elevator_data; struct request *rq; if (!list_empty(cfqd->dispatch)) { @@ -531,7 +531,7 @@ static void cfq_enqueue(struct cfq_data *cfqd, struct cfq_rq *crq) static void cfq_insert_request(request_queue_t *q, struct request *rq, int where) { - struct cfq_data *cfqd = q->elevator.elevator_data; + struct cfq_data *cfqd = q->elevator->elevator_data; struct cfq_rq *crq = RQ_DATA(rq); switch (where) { @@ -562,7 +562,7 @@ cfq_insert_request(request_queue_t *q, struct request *rq, int where) static int cfq_queue_empty(request_queue_t *q) { - struct cfq_data *cfqd = q->elevator.elevator_data; + struct cfq_data *cfqd = q->elevator->elevator_data; if (list_empty(cfqd->dispatch) && list_empty(&cfqd->rr_list)) return 1; @@ -596,7 +596,7 @@ cfq_latter_request(request_queue_t *q, struct request *rq) static int cfq_may_queue(request_queue_t *q, int rw) { - struct cfq_data *cfqd = q->elevator.elevator_data; + struct cfq_data *cfqd = q->elevator->elevator_data; struct cfq_queue *cfqq; int ret = 1; @@ -621,7 +621,7 @@ out: static void cfq_put_request(request_queue_t *q, struct request *rq) { - struct cfq_data *cfqd = q->elevator.elevator_data; + struct cfq_data *cfqd = q->elevator->elevator_data; struct cfq_rq *crq = RQ_DATA(rq); struct request_list *rl; int other_rw; @@ -654,7 +654,7 @@ static void cfq_put_request(request_queue_t *q, struct request *rq) static int cfq_set_request(request_queue_t *q, struct request *rq, int gfp_mask) { - struct cfq_data *cfqd = q->elevator.elevator_data; + struct cfq_data *cfqd = q->elevator->elevator_data; struct cfq_queue *cfqq; struct cfq_rq *crq; @@ -679,7 +679,7 @@ static int cfq_set_request(request_queue_t *q, struct request *rq, int gfp_mask) return 1; } -static void cfq_exit(request_queue_t *q, elevator_t *e) +static void cfq_exit_queue(elevator_t *e) { struct cfq_data *cfqd = e->elevator_data; @@ -690,7 +690,7 @@ static void cfq_exit(request_queue_t *q, elevator_t *e) kfree(cfqd); } -static int cfq_init(request_queue_t *q, elevator_t *e) +static int cfq_init_queue(request_queue_t *q, elevator_t *e) { struct cfq_data *cfqd; int i; @@ -732,7 +732,6 @@ static int cfq_init(request_queue_t *q, elevator_t *e) cfqd->cfq_queued = cfq_queued; cfqd->cfq_quantum = cfq_quantum; - return 0; out_crqpool: kfree(cfqd->cfq_hash); @@ -743,30 +742,38 @@ out_crqhash: return -ENOMEM; } -static int __init cfq_slab_setup(void) +static void cfq_slab_kill(void) +{ + if (crq_pool) + kmem_cache_destroy(crq_pool); + if (cfq_mpool) + mempool_destroy(cfq_mpool); + if (cfq_pool) + kmem_cache_destroy(cfq_pool); +} + +static int cfq_slab_setup(void) { crq_pool = kmem_cache_create("crq_pool", sizeof(struct cfq_rq), 0, 0, NULL, NULL); - if (!crq_pool) - panic("cfq_iosched: can't init crq pool\n"); + goto fail; cfq_pool = kmem_cache_create("cfq_pool", sizeof(struct cfq_queue), 0, 0, NULL, NULL); - if (!cfq_pool) - panic("cfq_iosched: can't init cfq pool\n"); + goto fail; cfq_mpool = mempool_create(64, mempool_alloc_slab, mempool_free_slab, cfq_pool); - if (!cfq_mpool) - panic("cfq_iosched: can't init cfq mpool\n"); + goto fail; return 0; +fail: + cfq_slab_kill(); + return -ENOMEM; } -subsys_initcall(cfq_slab_setup); - /* * sysfs parts below --> */ @@ -868,23 +875,51 @@ struct kobj_type cfq_ktype = { .default_attrs = default_attrs, }; -elevator_t iosched_cfq = { - .elevator_name = "cfq", - .elevator_ktype = &cfq_ktype, - .elevator_merge_fn = cfq_merge, - .elevator_merged_fn = cfq_merged_request, - .elevator_merge_req_fn = cfq_merged_requests, - .elevator_next_req_fn = cfq_next_request, - .elevator_add_req_fn = cfq_insert_request, - .elevator_remove_req_fn = cfq_remove_request, - .elevator_queue_empty_fn = cfq_queue_empty, - .elevator_former_req_fn = cfq_former_request, - .elevator_latter_req_fn = cfq_latter_request, - .elevator_set_req_fn = cfq_set_request, - .elevator_put_req_fn = cfq_put_request, - .elevator_may_queue_fn = cfq_may_queue, - .elevator_init_fn = cfq_init, - .elevator_exit_fn = cfq_exit, +static struct elevator_type iosched_cfq = { + .ops = { + .elevator_merge_fn = cfq_merge, + .elevator_merged_fn = cfq_merged_request, + .elevator_merge_req_fn = cfq_merged_requests, + .elevator_next_req_fn = cfq_next_request, + .elevator_add_req_fn = cfq_insert_request, + .elevator_remove_req_fn = cfq_remove_request, + .elevator_queue_empty_fn = cfq_queue_empty, + .elevator_former_req_fn = cfq_former_request, + .elevator_latter_req_fn = cfq_latter_request, + .elevator_set_req_fn = cfq_set_request, + .elevator_put_req_fn = cfq_put_request, + .elevator_may_queue_fn = cfq_may_queue, + .elevator_init_fn = cfq_init_queue, + .elevator_exit_fn = cfq_exit_queue, + }, + .elevator_ktype = &cfq_ktype, + .elevator_name = "cfq", + .elevator_owner = THIS_MODULE, }; -EXPORT_SYMBOL(iosched_cfq); +int cfq_init(void) +{ + int ret; + + if (cfq_slab_setup()) + return -ENOMEM; + + ret = elv_register(&iosched_cfq); + if (ret) + cfq_slab_kill(); + + return ret; +} + +void cfq_exit(void) +{ + cfq_slab_kill(); + elv_unregister(&iosched_cfq); +} + +module_init(cfq_init); +module_exit(cfq_exit); + +MODULE_AUTHOR("Jens Axboe"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Completely Fair Queueing IO scheduler"); diff --git a/drivers/block/deadline-iosched.c b/drivers/block/deadline-iosched.c index fb7ab733c709..0d3e2411f1d3 100644 --- a/drivers/block/deadline-iosched.c +++ b/drivers/block/deadline-iosched.c @@ -289,7 +289,7 @@ deadline_find_first_drq(struct deadline_data *dd, int data_dir) static inline void deadline_add_request(struct request_queue *q, struct request *rq) { - struct deadline_data *dd = q->elevator.elevator_data; + struct deadline_data *dd = q->elevator->elevator_data; struct deadline_rq *drq = RQ_DATA(rq); const int data_dir = rq_data_dir(drq->request); @@ -317,7 +317,7 @@ static void deadline_remove_request(request_queue_t *q, struct request *rq) struct deadline_rq *drq = RQ_DATA(rq); if (drq) { - struct deadline_data *dd = q->elevator.elevator_data; + struct deadline_data *dd = q->elevator->elevator_data; list_del_init(&drq->fifo); deadline_remove_merge_hints(q, drq); @@ -328,7 +328,7 @@ static void deadline_remove_request(request_queue_t *q, struct request *rq) static int deadline_merge(request_queue_t *q, struct request **req, struct bio *bio) { - struct deadline_data *dd = q->elevator.elevator_data; + struct deadline_data *dd = q->elevator->elevator_data; struct request *__rq; int ret; @@ -383,7 +383,7 @@ out_insert: static void deadline_merged_request(request_queue_t *q, struct request *req) { - struct deadline_data *dd = q->elevator.elevator_data; + struct deadline_data *dd = q->elevator->elevator_data; struct deadline_rq *drq = RQ_DATA(req); /* @@ -407,7 +407,7 @@ static void deadline_merged_requests(request_queue_t *q, struct request *req, struct request *next) { - struct deadline_data *dd = q->elevator.elevator_data; + struct deadline_data *dd = q->elevator->elevator_data; struct deadline_rq *drq = RQ_DATA(req); struct deadline_rq *dnext = RQ_DATA(next); @@ -604,7 +604,7 @@ dispatch_request: static struct request *deadline_next_request(request_queue_t *q) { - struct deadline_data *dd = q->elevator.elevator_data; + struct deadline_data *dd = q->elevator->elevator_data; struct request *rq; /* @@ -625,7 +625,7 @@ dispatch: static void deadline_insert_request(request_queue_t *q, struct request *rq, int where) { - struct deadline_data *dd = q->elevator.elevator_data; + struct deadline_data *dd = q->elevator->elevator_data; /* barriers must flush the reorder queue */ if (unlikely(rq->flags & (REQ_SOFTBARRIER | REQ_HARDBARRIER) @@ -653,7 +653,7 @@ deadline_insert_request(request_queue_t *q, struct request *rq, int where) static int deadline_queue_empty(request_queue_t *q) { - struct deadline_data *dd = q->elevator.elevator_data; + struct deadline_data *dd = q->elevator->elevator_data; if (!list_empty(&dd->fifo_list[WRITE]) || !list_empty(&dd->fifo_list[READ]) @@ -687,7 +687,7 @@ deadline_latter_request(request_queue_t *q, struct request *rq) return NULL; } -static void deadline_exit(request_queue_t *q, elevator_t *e) +static void deadline_exit_queue(elevator_t *e) { struct deadline_data *dd = e->elevator_data; @@ -703,7 +703,7 @@ static void deadline_exit(request_queue_t *q, elevator_t *e) * initialize elevator private data (deadline_data), and alloc a drq for * each request on the free lists */ -static int deadline_init(request_queue_t *q, elevator_t *e) +static int deadline_init_queue(request_queue_t *q, elevator_t *e) { struct deadline_data *dd; int i; @@ -748,7 +748,7 @@ static int deadline_init(request_queue_t *q, elevator_t *e) static void deadline_put_request(request_queue_t *q, struct request *rq) { - struct deadline_data *dd = q->elevator.elevator_data; + struct deadline_data *dd = q->elevator->elevator_data; struct deadline_rq *drq = RQ_DATA(rq); if (drq) { @@ -760,7 +760,7 @@ static void deadline_put_request(request_queue_t *q, struct request *rq) static int deadline_set_request(request_queue_t *q, struct request *rq, int gfp_mask) { - struct deadline_data *dd = q->elevator.elevator_data; + struct deadline_data *dd = q->elevator->elevator_data; struct deadline_rq *drq; drq = mempool_alloc(dd->drq_pool, gfp_mask); @@ -906,36 +906,54 @@ struct kobj_type deadline_ktype = { .default_attrs = default_attrs, }; -static int __init deadline_slab_setup(void) +static struct elevator_type iosched_deadline = { + .ops = { + .elevator_merge_fn = deadline_merge, + .elevator_merged_fn = deadline_merged_request, + .elevator_merge_req_fn = deadline_merged_requests, + .elevator_next_req_fn = deadline_next_request, + .elevator_add_req_fn = deadline_insert_request, + .elevator_remove_req_fn = deadline_remove_request, + .elevator_queue_empty_fn = deadline_queue_empty, + .elevator_former_req_fn = deadline_former_request, + .elevator_latter_req_fn = deadline_latter_request, + .elevator_set_req_fn = deadline_set_request, + .elevator_put_req_fn = deadline_put_request, + .elevator_init_fn = deadline_init_queue, + .elevator_exit_fn = deadline_exit_queue, + }, + + .elevator_ktype = &deadline_ktype, + .elevator_name = "deadline", + .elevator_owner = THIS_MODULE, +}; + +int deadline_init(void) { + int ret; + drq_pool = kmem_cache_create("deadline_drq", sizeof(struct deadline_rq), 0, 0, NULL, NULL); if (!drq_pool) - panic("deadline: can't init slab pool\n"); + return -ENOMEM; - return 0; + ret = elv_register(&iosched_deadline); + if (ret) + kmem_cache_destroy(drq_pool); + + return ret; } -subsys_initcall(deadline_slab_setup); - -elevator_t iosched_deadline = { - .elevator_merge_fn = deadline_merge, - .elevator_merged_fn = deadline_merged_request, - .elevator_merge_req_fn = deadline_merged_requests, - .elevator_next_req_fn = deadline_next_request, - .elevator_add_req_fn = deadline_insert_request, - .elevator_remove_req_fn = deadline_remove_request, - .elevator_queue_empty_fn = deadline_queue_empty, - .elevator_former_req_fn = deadline_former_request, - .elevator_latter_req_fn = deadline_latter_request, - .elevator_set_req_fn = deadline_set_request, - .elevator_put_req_fn = deadline_put_request, - .elevator_init_fn = deadline_init, - .elevator_exit_fn = deadline_exit, - - .elevator_ktype = &deadline_ktype, - .elevator_name = "deadline", -}; +void deadline_exit(void) +{ + kmem_cache_destroy(drq_pool); + elv_unregister(&iosched_deadline); +} + +module_init(deadline_init); +module_exit(deadline_exit); -EXPORT_SYMBOL(iosched_deadline); +MODULE_AUTHOR("Jens Axboe"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("deadline IO scheduler"); diff --git a/drivers/block/elevator.c b/drivers/block/elevator.c index 35c9385ac133..92cc7a9a5c63 100644 --- a/drivers/block/elevator.c +++ b/drivers/block/elevator.c @@ -37,6 +37,9 @@ #include +static spinlock_t elv_list_lock = SPIN_LOCK_UNLOCKED; +static LIST_HEAD(elv_list); + /* * can we safely merge with this request? */ @@ -60,6 +63,7 @@ inline int elv_rq_merge_ok(struct request *rq, struct bio *bio) return 0; } +EXPORT_SYMBOL(elv_rq_merge_ok); inline int elv_try_merge(struct request *__rq, struct bio *bio) { @@ -77,6 +81,7 @@ inline int elv_try_merge(struct request *__rq, struct bio *bio) return ret; } +EXPORT_SYMBOL(elv_try_merge); inline int elv_try_last_merge(request_queue_t *q, struct bio *bio) { @@ -85,31 +90,117 @@ inline int elv_try_last_merge(request_queue_t *q, struct bio *bio) return ELEVATOR_NO_MERGE; } +EXPORT_SYMBOL(elv_try_last_merge); -/* - * general block -> elevator interface starts here - */ -int elevator_init(request_queue_t *q, elevator_t *type) +struct elevator_type *elevator_find(const char *name) +{ + struct elevator_type *e = NULL; + struct list_head *entry; + + spin_lock_irq(&elv_list_lock); + list_for_each(entry, &elv_list) { + struct elevator_type *__e; + + __e = list_entry(entry, struct elevator_type, list); + + if (!strcmp(__e->elevator_name, name)) { + e = __e; + break; + } + } + spin_unlock_irq(&elv_list_lock); + + return e; +} + +static int elevator_attach(request_queue_t *q, struct elevator_type *e, + struct elevator_queue *eq) { - elevator_t *e = &q->elevator; + int ret = 0; - memcpy(e, type, sizeof(*e)); + if (!try_module_get(e->elevator_owner)) + return -EINVAL; + + memset(eq, 0, sizeof(*eq)); + eq->ops = &e->ops; + eq->elevator_type = e; INIT_LIST_HEAD(&q->queue_head); q->last_merge = NULL; + q->elevator = eq; + + if (eq->ops->elevator_init_fn) + ret = eq->ops->elevator_init_fn(q, eq); - if (e->elevator_init_fn) - return e->elevator_init_fn(q, e); + return ret; +} + +static char chosen_elevator[16]; + +static void elevator_setup_default(void) +{ + /* + * check if default is set and exists + */ + if (chosen_elevator[0] && elevator_find(chosen_elevator)) + return; + +#if defined(CONFIG_IOSCHED_AS) + strcpy(chosen_elevator, "anticipatory"); +#elif defined(CONFIG_IOSCHED_DEADLINE) + strcpy(chosen_elevator, "deadline"); +#elif defined(CONFIG_IOSCHED_CFQ) + strcpy(chosen_elevator, "cfq"); +#elif defined(CONFIG_IOSCHED_NOOP) + strcpy(chosen_elevator, "noop"); +#else +#error "You must build at least 1 IO scheduler into the kernel" +#endif + printk("elevator: using %s as default io scheduler\n", chosen_elevator); +} +static int __init elevator_setup(char *str) +{ + strncpy(chosen_elevator, str, sizeof(chosen_elevator) - 1); return 0; } -void elevator_exit(request_queue_t *q) +__setup("elevator=", elevator_setup); + +int elevator_init(request_queue_t *q, char *name) +{ + struct elevator_type *e = NULL; + struct elevator_queue *eq; + int ret = 0; + + elevator_setup_default(); + + if (!name) + name = chosen_elevator; + + e = elevator_find(name); + if (!e) + return -EINVAL; + + eq = kmalloc(sizeof(struct elevator_queue), GFP_KERNEL); + if (!eq) + return -ENOMEM; + + ret = elevator_attach(q, e, eq); + if (ret) + kfree(eq); + + return ret; +} + +void elevator_exit(elevator_t *e) { - elevator_t *e = &q->elevator; + if (e->ops->elevator_exit_fn) + e->ops->elevator_exit_fn(e); - if (e->elevator_exit_fn) - e->elevator_exit_fn(q, e); + module_put(e->elevator_type->elevator_owner); + e->elevator_type = NULL; + kfree(e); } int elevator_global_init(void) @@ -119,32 +210,32 @@ int elevator_global_init(void) int elv_merge(request_queue_t *q, struct request **req, struct bio *bio) { - elevator_t *e = &q->elevator; + elevator_t *e = q->elevator; - if (e->elevator_merge_fn) - return e->elevator_merge_fn(q, req, bio); + if (e->ops->elevator_merge_fn) + return e->ops->elevator_merge_fn(q, req, bio); return ELEVATOR_NO_MERGE; } void elv_merged_request(request_queue_t *q, struct request *rq) { - elevator_t *e = &q->elevator; + elevator_t *e = q->elevator; - if (e->elevator_merged_fn) - e->elevator_merged_fn(q, rq); + if (e->ops->elevator_merged_fn) + e->ops->elevator_merged_fn(q, rq); } void elv_merge_requests(request_queue_t *q, struct request *rq, struct request *next) { - elevator_t *e = &q->elevator; + elevator_t *e = q->elevator; if (q->last_merge == next) q->last_merge = NULL; - if (e->elevator_merge_req_fn) - e->elevator_merge_req_fn(q, rq, next); + if (e->ops->elevator_merge_req_fn) + e->ops->elevator_merge_req_fn(q, rq, next); } void elv_requeue_request(request_queue_t *q, struct request *rq) @@ -160,8 +251,8 @@ void elv_requeue_request(request_queue_t *q, struct request *rq) * if iosched has an explicit requeue hook, then use that. otherwise * just put the request at the front of the queue */ - if (q->elevator.elevator_requeue_req_fn) - q->elevator.elevator_requeue_req_fn(q, rq); + if (q->elevator->ops->elevator_requeue_req_fn) + q->elevator->ops->elevator_requeue_req_fn(q, rq); else __elv_add_request(q, rq, ELEVATOR_INSERT_FRONT, 0); } @@ -180,7 +271,7 @@ void __elv_add_request(request_queue_t *q, struct request *rq, int where, blk_plug_device(q); rq->q = q; - q->elevator.elevator_add_req_fn(q, rq, where); + q->elevator->ops->elevator_add_req_fn(q, rq, where); if (blk_queue_plugged(q)) { int nrq = q->rq.count[READ] + q->rq.count[WRITE] - q->in_flight; @@ -203,7 +294,7 @@ void elv_add_request(request_queue_t *q, struct request *rq, int where, static inline struct request *__elv_next_request(request_queue_t *q) { - return q->elevator.elevator_next_req_fn(q); + return q->elevator->ops->elevator_next_req_fn(q); } struct request *elv_next_request(request_queue_t *q) @@ -252,7 +343,7 @@ struct request *elv_next_request(request_queue_t *q) void elv_remove_request(request_queue_t *q, struct request *rq) { - elevator_t *e = &q->elevator; + elevator_t *e = q->elevator; /* * the time frame between a request being removed from the lists @@ -274,16 +365,16 @@ void elv_remove_request(request_queue_t *q, struct request *rq) if (rq == q->last_merge) q->last_merge = NULL; - if (e->elevator_remove_req_fn) - e->elevator_remove_req_fn(q, rq); + if (e->ops->elevator_remove_req_fn) + e->ops->elevator_remove_req_fn(q, rq); } int elv_queue_empty(request_queue_t *q) { - elevator_t *e = &q->elevator; + elevator_t *e = q->elevator; - if (e->elevator_queue_empty_fn) - return e->elevator_queue_empty_fn(q); + if (e->ops->elevator_queue_empty_fn) + return e->ops->elevator_queue_empty_fn(q); return list_empty(&q->queue_head); } @@ -292,10 +383,10 @@ struct request *elv_latter_request(request_queue_t *q, struct request *rq) { struct list_head *next; - elevator_t *e = &q->elevator; + elevator_t *e = q->elevator; - if (e->elevator_latter_req_fn) - return e->elevator_latter_req_fn(q, rq); + if (e->ops->elevator_latter_req_fn) + return e->ops->elevator_latter_req_fn(q, rq); next = rq->queuelist.next; if (next != &q->queue_head && next != &rq->queuelist) @@ -308,10 +399,10 @@ struct request *elv_former_request(request_queue_t *q, struct request *rq) { struct list_head *prev; - elevator_t *e = &q->elevator; + elevator_t *e = q->elevator; - if (e->elevator_former_req_fn) - return e->elevator_former_req_fn(q, rq); + if (e->ops->elevator_former_req_fn) + return e->ops->elevator_former_req_fn(q, rq); prev = rq->queuelist.prev; if (prev != &q->queue_head && prev != &rq->queuelist) @@ -322,10 +413,10 @@ struct request *elv_former_request(request_queue_t *q, struct request *rq) int elv_set_request(request_queue_t *q, struct request *rq, int gfp_mask) { - elevator_t *e = &q->elevator; + elevator_t *e = q->elevator; - if (e->elevator_set_req_fn) - return e->elevator_set_req_fn(q, rq, gfp_mask); + if (e->ops->elevator_set_req_fn) + return e->ops->elevator_set_req_fn(q, rq, gfp_mask); rq->elevator_private = NULL; return 0; @@ -333,25 +424,25 @@ int elv_set_request(request_queue_t *q, struct request *rq, int gfp_mask) void elv_put_request(request_queue_t *q, struct request *rq) { - elevator_t *e = &q->elevator; + elevator_t *e = q->elevator; - if (e->elevator_put_req_fn) - e->elevator_put_req_fn(q, rq); + if (e->ops->elevator_put_req_fn) + e->ops->elevator_put_req_fn(q, rq); } int elv_may_queue(request_queue_t *q, int rw) { - elevator_t *e = &q->elevator; + elevator_t *e = q->elevator; - if (e->elevator_may_queue_fn) - return e->elevator_may_queue_fn(q, rw); + if (e->ops->elevator_may_queue_fn) + return e->ops->elevator_may_queue_fn(q, rw); return 0; } void elv_completed_request(request_queue_t *q, struct request *rq) { - elevator_t *e = &q->elevator; + elevator_t *e = q->elevator; /* * request is released from the driver, io must be done @@ -359,22 +450,20 @@ void elv_completed_request(request_queue_t *q, struct request *rq) if (blk_account_rq(rq)) q->in_flight--; - if (e->elevator_completed_req_fn) - e->elevator_completed_req_fn(q, rq); + if (e->ops->elevator_completed_req_fn) + e->ops->elevator_completed_req_fn(q, rq); } int elv_register_queue(struct request_queue *q) { - elevator_t *e; - - e = &q->elevator; + elevator_t *e = q->elevator; e->kobj.parent = kobject_get(&q->kobj); if (!e->kobj.parent) return -EBUSY; snprintf(e->kobj.name, KOBJ_NAME_LEN, "%s", "iosched"); - e->kobj.ktype = e->elevator_ktype; + e->kobj.ktype = e->elevator_type->elevator_ktype; return kobject_register(&e->kobj); } @@ -382,12 +471,131 @@ int elv_register_queue(struct request_queue *q) void elv_unregister_queue(struct request_queue *q) { if (q) { - elevator_t * e = &q->elevator; + elevator_t *e = q->elevator; kobject_unregister(&e->kobj); kobject_put(&q->kobj); } } +int elv_register(struct elevator_type *e) +{ + if (elevator_find(e->elevator_name)) + BUG(); + + spin_lock_irq(&elv_list_lock); + list_add_tail(&e->list, &elv_list); + spin_unlock_irq(&elv_list_lock); + + printk("io scheduler %s registered\n", e->elevator_name); + return 0; +} +EXPORT_SYMBOL_GPL(elv_register); + +void elv_unregister(struct elevator_type *e) +{ + spin_lock_irq(&elv_list_lock); + list_del_init(&e->list); + spin_unlock_irq(&elv_list_lock); +} +EXPORT_SYMBOL_GPL(elv_unregister); + +/* + * switch to new_e io scheduler. be careful not to introduce deadlocks - + * we don't free the old io scheduler, before we have allocated what we + * need for the new one. this way we have a chance of going back to the old + * one, if the new one fails init for some reason + */ +static void elevator_switch(request_queue_t *q, struct elevator_type *new_e) +{ + elevator_t *e = kmalloc(sizeof(elevator_t), GFP_KERNEL); + elevator_t *old_elevator; + + if (!e) { + printk("elevator: out of memory\n"); + return; + } + + blk_wait_queue_drained(q); + + /* + * unregister old elevator data + */ + elv_unregister_queue(q); + old_elevator = q->elevator; + + /* + * attach and start new elevator + */ + if (elevator_attach(q, new_e, e)) + goto fail; + + if (elv_register_queue(q)) + goto fail_register; + + /* + * finally exit old elevator and start queue again + */ + elevator_exit(old_elevator); + blk_finish_queue_drain(q); + return; + +fail_register: + /* + * switch failed, exit the new io scheduler and reattach the old + * one again (along with re-adding the sysfs dir) + */ + elevator_exit(e); +fail: + q->elevator = old_elevator; + elv_register_queue(q); + blk_finish_queue_drain(q); + printk("elevator: switch to %s failed\n", new_e->elevator_name); +} + +ssize_t elv_iosched_store(request_queue_t *q, const char *name, size_t count) +{ + char elevator_name[ELV_NAME_MAX]; + struct elevator_type *e; + + memset(elevator_name, 0, sizeof(elevator_name)); + strncpy(elevator_name, name, sizeof(elevator_name)); + + if (elevator_name[strlen(elevator_name) - 1] == '\n') + elevator_name[strlen(elevator_name) - 1] = '\0'; + + e = elevator_find(elevator_name); + if (!e) { + printk("elevator: type %s not found\n", elevator_name); + return -EINVAL; + } + + elevator_switch(q, e); + return count; +} + +ssize_t elv_iosched_show(request_queue_t *q, char *name) +{ + elevator_t *e = q->elevator; + struct elevator_type *elv = e->elevator_type; + struct list_head *entry; + int len = 0; + + spin_lock_irq(q->queue_lock); + list_for_each(entry, &elv_list) { + struct elevator_type *__e; + + __e = list_entry(entry, struct elevator_type, list); + if (!strcmp(elv->elevator_name, __e->elevator_name)) + len += sprintf(name+len, "[%s] ", elv->elevator_name); + else + len += sprintf(name+len, "%s ", __e->elevator_name); + } + spin_unlock_irq(q->queue_lock); + + len += sprintf(len+name, "\n"); + return len; +} + module_init(elevator_global_init); EXPORT_SYMBOL(elv_add_request); diff --git a/drivers/block/ll_rw_blk.c b/drivers/block/ll_rw_blk.c index 26fdf6be6bd0..b3780ca0fdc0 100644 --- a/drivers/block/ll_rw_blk.c +++ b/drivers/block/ll_rw_blk.c @@ -1395,7 +1395,8 @@ void blk_cleanup_queue(request_queue_t * q) if (!atomic_dec_and_test(&q->refcnt)) return; - elevator_exit(q); + if (q->elevator) + elevator_exit(q->elevator); del_timer_sync(&q->unplug_timer); kblockd_flush(); @@ -1418,6 +1419,7 @@ static int blk_init_free_list(request_queue_t *q) rl->count[READ] = rl->count[WRITE] = 0; init_waitqueue_head(&rl->wait[READ]); init_waitqueue_head(&rl->wait[WRITE]); + init_waitqueue_head(&rl->drain); rl->rq_pool = mempool_create(BLKDEV_MIN_RQ, mempool_alloc_slab, mempool_free_slab, request_cachep); @@ -1429,45 +1431,6 @@ static int blk_init_free_list(request_queue_t *q) static int __make_request(request_queue_t *, struct bio *); -static elevator_t *chosen_elevator = -#if defined(CONFIG_IOSCHED_AS) - &iosched_as; -#elif defined(CONFIG_IOSCHED_DEADLINE) - &iosched_deadline; -#elif defined(CONFIG_IOSCHED_CFQ) - &iosched_cfq; -#elif defined(CONFIG_IOSCHED_NOOP) - &elevator_noop; -#else - NULL; -#error "You must have at least 1 I/O scheduler selected" -#endif - -#if defined(CONFIG_IOSCHED_AS) || defined(CONFIG_IOSCHED_DEADLINE) || defined (CONFIG_IOSCHED_NOOP) -static int __init elevator_setup(char *str) -{ -#ifdef CONFIG_IOSCHED_DEADLINE - if (!strcmp(str, "deadline")) - chosen_elevator = &iosched_deadline; -#endif -#ifdef CONFIG_IOSCHED_AS - if (!strcmp(str, "as")) - chosen_elevator = &iosched_as; -#endif -#ifdef CONFIG_IOSCHED_CFQ - if (!strcmp(str, "cfq")) - chosen_elevator = &iosched_cfq; -#endif -#ifdef CONFIG_IOSCHED_NOOP - if (!strcmp(str, "noop")) - chosen_elevator = &elevator_noop; -#endif - return 1; -} - -__setup("elevator=", elevator_setup); -#endif /* CONFIG_IOSCHED_AS || CONFIG_IOSCHED_DEADLINE || CONFIG_IOSCHED_NOOP */ - request_queue_t *blk_alloc_queue(int gfp_mask) { request_queue_t *q = kmem_cache_alloc(requestq_cachep, gfp_mask); @@ -1520,21 +1483,14 @@ EXPORT_SYMBOL(blk_alloc_queue); **/ request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock) { - request_queue_t *q; - static int printed; + request_queue_t *q = blk_alloc_queue(GFP_KERNEL); - q = blk_alloc_queue(GFP_KERNEL); if (!q) return NULL; if (blk_init_free_list(q)) goto out_init; - if (!printed) { - printed = 1; - printk("Using %s io scheduler\n", chosen_elevator->elevator_name); - } - q->request_fn = rfn; q->back_merge_fn = ll_back_merge_fn; q->front_merge_fn = ll_front_merge_fn; @@ -1555,7 +1511,7 @@ request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock) /* * all done */ - if (!elevator_init(q, chosen_elevator)) + if (!elevator_init(q, NULL)) return q; blk_cleanup_queue(q); @@ -1649,6 +1605,9 @@ static void freed_request(request_queue_t *q, int rw) if (!waitqueue_active(&rl->wait[rw])) blk_clear_queue_full(q, rw); } + if (unlikely(waitqueue_active(&rl->drain)) && + !rl->count[READ] && !rl->count[WRITE]) + wake_up(&rl->drain); } #define blkdev_free_rq(list) list_entry((list)->next, struct request, queuelist) @@ -1661,6 +1620,9 @@ static struct request *get_request(request_queue_t *q, int rw, int gfp_mask) struct request_list *rl = &q->rq; struct io_context *ioc = get_io_context(gfp_mask); + if (unlikely(test_bit(QUEUE_FLAG_DRAIN, &q->queue_flags))) + return NULL; + spin_lock_irq(q->queue_lock); if (rl->count[rw]+1 >= q->nr_requests) { /* @@ -2506,6 +2468,70 @@ static inline void blk_partition_remap(struct bio *bio) } } +void blk_finish_queue_drain(request_queue_t *q) +{ + struct request_list *rl = &q->rq; + + clear_bit(QUEUE_FLAG_DRAIN, &q->queue_flags); + wake_up(&rl->wait[0]); + wake_up(&rl->wait[1]); + wake_up(&rl->drain); +} + +/* + * We rely on the fact that only requests allocated through blk_alloc_request() + * have io scheduler private data structures associated with them. Any other + * type of request (allocated on stack or through kmalloc()) should not go + * to the io scheduler core, but be attached to the queue head instead. + */ +void blk_wait_queue_drained(request_queue_t *q) +{ + struct request_list *rl = &q->rq; + DEFINE_WAIT(wait); + + spin_lock_irq(q->queue_lock); + set_bit(QUEUE_FLAG_DRAIN, &q->queue_flags); + + while (rl->count[READ] || rl->count[WRITE]) { + prepare_to_wait(&rl->drain, &wait, TASK_UNINTERRUPTIBLE); + + if (rl->count[READ] || rl->count[WRITE]) { + __generic_unplug_device(q); + spin_unlock_irq(q->queue_lock); + io_schedule(); + spin_lock_irq(q->queue_lock); + } + + finish_wait(&rl->drain, &wait); + } + + spin_unlock_irq(q->queue_lock); +} + +/* + * block waiting for the io scheduler being started again. + */ +static inline void block_wait_queue_running(request_queue_t *q) +{ + DEFINE_WAIT(wait); + + while (test_bit(QUEUE_FLAG_DRAIN, &q->queue_flags)) { + struct request_list *rl = &q->rq; + + prepare_to_wait_exclusive(&rl->drain, &wait, + TASK_UNINTERRUPTIBLE); + + /* + * re-check the condition. avoids using prepare_to_wait() + * in the fast path (queue is running) + */ + if (test_bit(QUEUE_FLAG_DRAIN, &q->queue_flags)) + io_schedule(); + + finish_wait(&rl->drain, &wait); + } +} + /** * generic_make_request: hand a buffer to its device driver for I/O * @bio: The bio describing the location in memory and on the device. @@ -2595,6 +2621,8 @@ end_io: if (test_bit(QUEUE_FLAG_DEAD, &q->queue_flags)) goto end_io; + block_wait_queue_running(q); + /* * If this device has partitions, remap block n * of partition p to block n+start(p) of the disk. @@ -3018,6 +3046,7 @@ void kblockd_flush(void) { flush_workqueue(kblockd_workqueue); } +EXPORT_SYMBOL(kblockd_flush); int __init blk_dev_init(void) { @@ -3036,6 +3065,7 @@ int __init blk_dev_init(void) blk_max_low_pfn = max_low_pfn; blk_max_pfn = max_pfn; + return 0; } @@ -3055,6 +3085,7 @@ void put_io_context(struct io_context *ioc) kmem_cache_free(iocontext_cachep, ioc); } } +EXPORT_SYMBOL(put_io_context); /* Called by the exitting task */ void exit_io_context(void) @@ -3106,6 +3137,7 @@ struct io_context *get_io_context(int gfp_flags) local_irq_restore(flags); return ret; } +EXPORT_SYMBOL(get_io_context); void copy_io_context(struct io_context **pdst, struct io_context **psrc) { @@ -3119,6 +3151,7 @@ void copy_io_context(struct io_context **pdst, struct io_context **psrc) *pdst = src; } } +EXPORT_SYMBOL(copy_io_context); void swap_io_context(struct io_context **ioc1, struct io_context **ioc2) { @@ -3127,7 +3160,7 @@ void swap_io_context(struct io_context **ioc1, struct io_context **ioc2) *ioc1 = *ioc2; *ioc2 = temp; } - +EXPORT_SYMBOL(swap_io_context); /* * sysfs parts below @@ -3285,11 +3318,18 @@ static struct queue_sysfs_entry queue_max_hw_sectors_entry = { .show = queue_max_hw_sectors_show, }; +static struct queue_sysfs_entry queue_iosched_entry = { + .attr = {.name = "scheduler", .mode = S_IRUGO | S_IWUSR }, + .show = elv_iosched_show, + .store = elv_iosched_store, +}; + static struct attribute *default_attrs[] = { &queue_requests_entry.attr, &queue_ra_entry.attr, &queue_max_hw_sectors_entry.attr, &queue_max_sectors_entry.attr, + &queue_iosched_entry.attr, NULL, }; diff --git a/drivers/block/noop-iosched.c b/drivers/block/noop-iosched.c index ffef40be1f92..707dddd7d881 100644 --- a/drivers/block/noop-iosched.c +++ b/drivers/block/noop-iosched.c @@ -83,12 +83,31 @@ struct request *elevator_noop_next_request(request_queue_t *q) return NULL; } -elevator_t elevator_noop = { - .elevator_merge_fn = elevator_noop_merge, - .elevator_merge_req_fn = elevator_noop_merge_requests, - .elevator_next_req_fn = elevator_noop_next_request, - .elevator_add_req_fn = elevator_noop_add_request, - .elevator_name = "noop", +static struct elevator_type elevator_noop = { + .ops = { + .elevator_merge_fn = elevator_noop_merge, + .elevator_merge_req_fn = elevator_noop_merge_requests, + .elevator_next_req_fn = elevator_noop_next_request, + .elevator_add_req_fn = elevator_noop_add_request, + }, + .elevator_name = "noop", + .elevator_owner = THIS_MODULE, }; -EXPORT_SYMBOL(elevator_noop); +int noop_init(void) +{ + return elv_register(&elevator_noop); +} + +void noop_exit(void) +{ + elv_unregister(&elevator_noop); +} + +module_init(noop_init); +module_exit(noop_exit); + + +MODULE_AUTHOR("Jens Axboe"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("No-op IO scheduler"); diff --git a/drivers/s390/block/dasd.c b/drivers/s390/block/dasd.c index 2249b78487bd..b3714fbd0083 100644 --- a/drivers/s390/block/dasd.c +++ b/drivers/s390/block/dasd.c @@ -1595,8 +1595,8 @@ dasd_alloc_queue(struct dasd_device * device) device->request_queue->queuedata = device; #if 0 - elevator_exit(device->request_queue); - rc = elevator_init(device->request_queue, &elevator_noop); + elevator_exit(device->request_queue->elevator); + rc = elevator_init(device->request_queue, "noop"); if (rc) { blk_cleanup_queue(device->request_queue); return rc; diff --git a/drivers/s390/char/tape_block.c b/drivers/s390/char/tape_block.c index b7f4e7b8be74..1efc9f21229e 100644 --- a/drivers/s390/char/tape_block.c +++ b/drivers/s390/char/tape_block.c @@ -225,8 +225,8 @@ tapeblock_setup_device(struct tape_device * device) if (!blkdat->request_queue) return -ENOMEM; - elevator_exit(blkdat->request_queue); - rc = elevator_init(blkdat->request_queue, &elevator_noop); + elevator_exit(blkdat->request_queue->elevator); + rc = elevator_init(blkdat->request_queue, "noop"); if (rc) goto cleanup_queue; diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h index 4efe45d1af7e..5e4a6ab84ecb 100644 --- a/include/linux/blkdev.h +++ b/include/linux/blkdev.h @@ -19,8 +19,8 @@ struct request_queue; typedef struct request_queue request_queue_t; -struct elevator_s; -typedef struct elevator_s elevator_t; +struct elevator_queue; +typedef struct elevator_queue elevator_t; struct request_pm_state; #define BLKDEV_MIN_RQ 4 @@ -80,6 +80,7 @@ struct request_list { int count[2]; mempool_t *rq_pool; wait_queue_head_t wait[2]; + wait_queue_head_t drain; }; #define BLK_MAX_CDB 16 @@ -279,7 +280,7 @@ struct request_queue */ struct list_head queue_head; struct request *last_merge; - elevator_t elevator; + elevator_t *elevator; /* * the queue request freelist, one for reads and one for writes @@ -381,6 +382,7 @@ struct request_queue #define QUEUE_FLAG_REENTER 6 /* Re-entrancy avoidance */ #define QUEUE_FLAG_PLUGGED 7 /* queue is plugged */ #define QUEUE_FLAG_ORDERED 8 /* supports ordered writes */ +#define QUEUE_FLAG_DRAIN 9 /* draining queue for sched switch */ #define blk_queue_plugged(q) test_bit(QUEUE_FLAG_PLUGGED, &(q)->queue_flags) #define blk_queue_tagged(q) test_bit(QUEUE_FLAG_QUEUED, &(q)->queue_flags) @@ -617,6 +619,8 @@ extern void blk_dump_rq_flags(struct request *, char *); extern void generic_unplug_device(request_queue_t *); extern void __generic_unplug_device(request_queue_t *); extern long nr_blockdev_pages(void); +extern void blk_wait_queue_drained(request_queue_t *); +extern void blk_finish_queue_drain(request_queue_t *); int blk_get_queue(request_queue_t *); request_queue_t *blk_alloc_queue(int); diff --git a/include/linux/elevator.h b/include/linux/elevator.h index 27e8183f4776..95cdfb5bb790 100644 --- a/include/linux/elevator.h +++ b/include/linux/elevator.h @@ -22,9 +22,9 @@ typedef int (elevator_set_req_fn) (request_queue_t *, struct request *, int); typedef void (elevator_put_req_fn) (request_queue_t *, struct request *); typedef int (elevator_init_fn) (request_queue_t *, elevator_t *); -typedef void (elevator_exit_fn) (request_queue_t *, elevator_t *); +typedef void (elevator_exit_fn) (elevator_t *); -struct elevator_s +struct elevator_ops { elevator_merge_fn *elevator_merge_fn; elevator_merged_fn *elevator_merged_fn; @@ -48,12 +48,32 @@ struct elevator_s elevator_init_fn *elevator_init_fn; elevator_exit_fn *elevator_exit_fn; +}; - void *elevator_data; +#define ELV_NAME_MAX (16) - struct kobject kobj; +/* + * identifies an elevator type, such as AS or deadline + */ +struct elevator_type +{ + struct list_head list; + struct elevator_ops ops; + struct elevator_type *elevator_type; struct kobj_type *elevator_ktype; - const char *elevator_name; + char elevator_name[ELV_NAME_MAX]; + struct module *elevator_owner; +}; + +/* + * each queue has an elevator_queue assoicated with it + */ +struct elevator_queue +{ + struct elevator_ops *ops; + void *elevator_data; + struct kobject kobj; + struct elevator_type *elevator_type; }; /* @@ -79,28 +99,19 @@ extern int elv_set_request(request_queue_t *, struct request *, int); extern void elv_put_request(request_queue_t *, struct request *); /* - * noop I/O scheduler. always merges, always inserts new request at tail - */ -extern elevator_t elevator_noop; - -/* - * deadline i/o scheduler. uses request time outs to prevent indefinite - * starvation - */ -extern elevator_t iosched_deadline; - -/* - * anticipatory I/O scheduler + * io scheduler registration */ -extern elevator_t iosched_as; +extern int elv_register(struct elevator_type *); +extern void elv_unregister(struct elevator_type *); /* - * completely fair queueing I/O scheduler + * io scheduler sysfs switching */ -extern elevator_t iosched_cfq; +extern ssize_t elv_iosched_show(request_queue_t *, char *); +extern ssize_t elv_iosched_store(request_queue_t *, const char *, size_t); -extern int elevator_init(request_queue_t *, elevator_t *); -extern void elevator_exit(request_queue_t *); +extern int elevator_init(request_queue_t *, char *); +extern void elevator_exit(elevator_t *); extern int elv_rq_merge_ok(struct request *, struct bio *); extern int elv_try_merge(struct request *, struct bio *); extern int elv_try_last_merge(request_queue_t *, struct bio *); -- cgit v1.2.3 From f9887e4a0cc489b33776d43be7362c1284e68819 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Mon, 18 Oct 2004 18:01:41 -0700 Subject: [PATCH] cfq-v2 I/O scheduler update Here is the next incarnation of the CFQ io scheduler, so far known as CFQ v2 locally. It attempts to address some of the limitations of the original CFQ io scheduler (hence forth known as CFQ v1). Some of the problems with CFQ v1 are: - It does accounting for the lifetime of the cfq_queue, which is setup and torn down for the time when a process has io in flight. For a fork heavy work load (such as a kernel compile, for instance), new processes can effectively starve io of running processes. This is in part due to the fact that CFQ v1 gives preference to a new processes to get better latency numbers. Removing that heuristic is not an option exactly because of that. - It makes no attempts to address inter-cfq_queue fairness. - It makes no attempt to limit upper latency bound of a single request. - It only provides per-tgid grouping. You need to change the source to group on a different criteria. - It uses a mempool for the cfq_queues. Theoretically this could deadlock if io bound processes never exit. - The may_queue() logic can be unfair since it fluctuates quickly, thus leaving processes sleeping while new processes are allowed to allocate a request. CFQ v2 attempts to fix these issues. It uses the process io_context logic to maintain a cfq_queue lifetime of the duration of the process (and its io). This means we can now be a lot more clever in deciding which process is allowed to queue or dispatch io to the device. The cfq_io_context is per-process per-queue, this is an extension to what AS currently does in that we truly do have a unique per-process identifier for io grouping. Busy queues are sorted by service time used, sub sorted by in_flight requests. Queues that have no io in flight are also preferred at dispatch time. Accounting is done on completion time of a request, or with a fixed cost for tagged command queueing. Requests are fifo'ed like with deadline, to make sure that a single request doesn't stay in the io scheduler for ages. Process grouping is selectable at runtime. I provide 4 grouping criterias: process group, thread group id, user id, and group id. As usual, settings are sysfs tweakable in /sys/block//queue/iosched axboe@apu:[.]s/block/hda/queue/iosched $ ls back_seek_max fifo_batch_expire find_best_crq queued back_seek_penalty fifo_expire_async key_type show_status clear_elapsed fifo_expire_sync quantum tagged In order, each of these settings control: back_seek_max back_seek_penalty: Useful logic stolen from AS that allow small backwards seeks in the io stream if we deem them useful. CFQ uses a strict ascending elevator otherwise. _max controls the maximum allowed backwards seek, defaulting to 16MiB. _penalty denotes how expensive we account a backwards seek compared to a forward seek. Default is 2, meaning it's twice as expensive. clear_elapsed: Really a debug switch, will go away in the future. It clears the maximum values for completion and dispatch time, shown in show_status. fifo_batch_expire fifo_batch_async fifo_batch_sync: The settings for the expiry fifo. batch_expire is how often we allow the fifo expire to control which request to select. Default is 125ms. _async is the deadline for async requests (typically writes), _sync is the deadline for sync requests (reads and sync writes). Defaults are, respectively, 5 seconds and 0.5 seconds. key_type: The grouping key. Can be set to pgid, tgid, uid, or gid. The current value is shown bracketed: axboe@apu:[.]s/block/hda/queue/iosched $ cat key_type [pgid] tgid uid gid Default is tgid. To set, simply echo any of the 4 words into the file. quantum: The amount of requests we select for dispatch when the driver asks for work to do and the current pending list is empty. Default is 4. queued: The minimum amount of requests a group is allowed to queue. Default is 8. show_status: Debug output showing the current state of the queues. tagged: Set this to 1 if the device is using tagged command queueing. This cannot be reliably detected by CFQ yet, since most drivers don't use the block layer (well it could, by looking at number of requests being between dispatch and completion. but not completely reliably). Default is 0. The patch is a little big, but works reliably here on my laptop. There are a number of other changes and fixes in there (like converting to hlist for hashes). The code is commented a lot better, CFQ v1 has basically no comments (reflecting that it was writting in one go, no touched or tuned much since then). This is of course only done to increase the AAF, akpm acceptance factor. Since I'm on the road, I cannot provide any really good numbers of CFQ v1 compared to v2, maybe someone will help me out there. Signed-off-by: Jens Axboe Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/block/as-iosched.c | 4 +- drivers/block/cfq-iosched.c | 1459 ++++++++++++++++++++++++++++++++++++------- drivers/block/elevator.c | 2 +- drivers/block/ll_rw_blk.c | 115 ++-- include/linux/blkdev.h | 18 + include/linux/elevator.h | 9 + 6 files changed, 1337 insertions(+), 270 deletions(-) diff --git a/drivers/block/as-iosched.c b/drivers/block/as-iosched.c index bb3e9b5bab3c..b049848c19c3 100644 --- a/drivers/block/as-iosched.c +++ b/drivers/block/as-iosched.c @@ -1828,14 +1828,14 @@ static int as_set_request(request_queue_t *q, struct request *rq, int gfp_mask) static int as_may_queue(request_queue_t *q, int rw) { - int ret = 0; + int ret = ELV_MQUEUE_MAY; struct as_data *ad = q->elevator->elevator_data; struct io_context *ioc; if (ad->antic_status == ANTIC_WAIT_REQ || ad->antic_status == ANTIC_WAIT_NEXT) { ioc = as_get_io_context(); if (ad->io_context == ioc) - ret = 1; + ret = ELV_MQUEUE_MUST; put_io_context(ioc); } diff --git a/drivers/block/cfq-iosched.c b/drivers/block/cfq-iosched.c index 6a424dc65823..738ff90bb2c1 100644 --- a/drivers/block/cfq-iosched.c +++ b/drivers/block/cfq-iosched.c @@ -22,96 +22,216 @@ #include #include +static unsigned long max_elapsed_crq; +static unsigned long max_elapsed_dispatch; + /* * tunables */ -static int cfq_quantum = 4; -static int cfq_queued = 8; +static int cfq_quantum = 4; /* max queue in one round of service */ +static int cfq_queued = 8; /* minimum rq allocate limit per-queue*/ +static int cfq_service = HZ; /* period over which service is avg */ +static int cfq_fifo_expire_r = HZ / 2; /* fifo timeout for sync requests */ +static int cfq_fifo_expire_w = 5 * HZ; /* fifo timeout for async requests */ +static int cfq_fifo_rate = HZ / 8; /* fifo expiry rate */ +static int cfq_back_max = 16 * 1024; /* maximum backwards seek, in KiB */ +static int cfq_back_penalty = 2; /* penalty of a backwards seek */ +/* + * for the hash of cfqq inside the cfqd + */ #define CFQ_QHASH_SHIFT 6 #define CFQ_QHASH_ENTRIES (1 << CFQ_QHASH_SHIFT) -#define list_entry_qhash(entry) list_entry((entry), struct cfq_queue, cfq_hash) +#define list_entry_qhash(entry) hlist_entry((entry), struct cfq_queue, cfq_hash) -#define CFQ_MHASH_SHIFT 8 +/* + * for the hash of crq inside the cfqq + */ +#define CFQ_MHASH_SHIFT 6 #define CFQ_MHASH_BLOCK(sec) ((sec) >> 3) #define CFQ_MHASH_ENTRIES (1 << CFQ_MHASH_SHIFT) -#define CFQ_MHASH_FN(sec) (hash_long(CFQ_MHASH_BLOCK((sec)),CFQ_MHASH_SHIFT)) -#define ON_MHASH(crq) !list_empty(&(crq)->hash) +#define CFQ_MHASH_FN(sec) hash_long(CFQ_MHASH_BLOCK(sec), CFQ_MHASH_SHIFT) #define rq_hash_key(rq) ((rq)->sector + (rq)->nr_sectors) -#define list_entry_hash(ptr) list_entry((ptr), struct cfq_rq, hash) +#define list_entry_hash(ptr) hlist_entry((ptr), struct cfq_rq, hash) #define list_entry_cfqq(ptr) list_entry((ptr), struct cfq_queue, cfq_list) -#define RQ_DATA(rq) ((struct cfq_rq *) (rq)->elevator_private) +#define RQ_DATA(rq) (rq)->elevator_private + +/* + * rb-tree defines + */ +#define RB_NONE (2) +#define RB_EMPTY(node) ((node)->rb_node == NULL) +#define RB_CLEAR_COLOR(node) (node)->rb_color = RB_NONE +#define RB_CLEAR(node) do { \ + (node)->rb_parent = NULL; \ + RB_CLEAR_COLOR((node)); \ + (node)->rb_right = NULL; \ + (node)->rb_left = NULL; \ +} while (0) +#define RB_CLEAR_ROOT(root) ((root)->rb_node = NULL) +#define ON_RB(node) ((node)->rb_color != RB_NONE) +#define rb_entry_crq(node) rb_entry((node), struct cfq_rq, rb_node) +#define rq_rb_key(rq) (rq)->sector + +/* + * threshold for switching off non-tag accounting + */ +#define CFQ_MAX_TAG (4) + +/* + * sort key types and names + */ +enum { + CFQ_KEY_PGID, + CFQ_KEY_TGID, + CFQ_KEY_UID, + CFQ_KEY_GID, + CFQ_KEY_LAST, +}; + +static char *cfq_key_types[] = { "pgid", "tgid", "uid", "gid", NULL }; + +/* + * spare queue + */ +#define CFQ_KEY_SPARE (~0UL) static kmem_cache_t *crq_pool; static kmem_cache_t *cfq_pool; -static mempool_t *cfq_mpool; +static kmem_cache_t *cfq_ioc_pool; struct cfq_data { struct list_head rr_list; - struct list_head *dispatch; - struct list_head *cfq_hash; + struct list_head empty_list; - struct list_head *crq_hash; + struct hlist_head *cfq_hash; + struct hlist_head *crq_hash; + /* queues on rr_list (ie they have pending requests */ unsigned int busy_queues; + unsigned int max_queued; + atomic_t ref; + + int key_type; + mempool_t *crq_pool; request_queue_t *queue; + sector_t last_sector; + + int rq_in_driver; + /* - * tunables + * tunables, see top of file */ unsigned int cfq_quantum; unsigned int cfq_queued; + unsigned int cfq_fifo_expire_r; + unsigned int cfq_fifo_expire_w; + unsigned int cfq_fifo_batch_expire; + unsigned int cfq_back_penalty; + unsigned int cfq_back_max; + unsigned int find_best_crq; + + unsigned int cfq_tagged; }; struct cfq_queue { - struct list_head cfq_hash; + /* reference count */ + atomic_t ref; + /* parent cfq_data */ + struct cfq_data *cfqd; + /* hash of mergeable requests */ + struct hlist_node cfq_hash; + /* hash key */ + unsigned long key; + /* whether queue is on rr (or empty) list */ + int on_rr; + /* on either rr or empty list of cfqd */ struct list_head cfq_list; + /* sorted list of pending requests */ struct rb_root sort_list; - int pid; + /* if fifo isn't expired, next request to serve */ + struct cfq_rq *next_crq; + /* requests queued in sort_list */ int queued[2]; -#if 0 - /* - * with a simple addition like this, we can do io priorities. almost. - * does need a split request free list, too. - */ - int io_prio -#endif + /* currently allocated requests */ + int allocated[2]; + /* fifo list of requests in sort_list */ + struct list_head fifo[2]; + /* last time fifo expired */ + unsigned long last_fifo_expire; + + int key_type; + + unsigned long service_start; + unsigned long service_used; + + unsigned int max_rate; + + /* number of requests that have been handed to the driver */ + int in_flight; + /* number of currently allocated requests */ + int alloc_limit[2]; }; struct cfq_rq { struct rb_node rb_node; sector_t rb_key; - struct request *request; + struct hlist_node hash; struct cfq_queue *cfq_queue; + struct cfq_io_context *io_context; + + unsigned long service_start; + unsigned long queue_start; - struct list_head hash; + unsigned int in_flight : 1; + unsigned int accounted : 1; + unsigned int is_sync : 1; + unsigned int is_write : 1; }; -static void cfq_put_queue(struct cfq_data *cfqd, struct cfq_queue *cfqq); -static struct cfq_queue *cfq_find_cfq_hash(struct cfq_data *cfqd, int pid); -static void cfq_dispatch_sort(struct cfq_data *cfqd, struct cfq_queue *cfqq, - struct cfq_rq *crq); +static struct cfq_queue *cfq_find_cfq_hash(struct cfq_data *, unsigned long); +static void cfq_dispatch_sort(request_queue_t *, struct cfq_rq *); +static void cfq_update_next_crq(struct cfq_rq *); +static void cfq_put_cfqd(struct cfq_data *cfqd); /* - * lots of deadline iosched dupes, can be abstracted later... + * what the fairness is based on (ie how processes are grouped and + * differentiated) */ -static inline void __cfq_del_crq_hash(struct cfq_rq *crq) +static inline unsigned long +cfq_hash_key(struct cfq_data *cfqd, struct task_struct *tsk) { - list_del_init(&crq->hash); + /* + * optimize this so that ->key_type is the offset into the struct + */ + switch (cfqd->key_type) { + case CFQ_KEY_PGID: + return process_group(tsk); + default: + case CFQ_KEY_TGID: + return tsk->tgid; + case CFQ_KEY_UID: + return tsk->uid; + case CFQ_KEY_GID: + return tsk->gid; + } } +/* + * lots of deadline iosched dupes, can be abstracted later... + */ static inline void cfq_del_crq_hash(struct cfq_rq *crq) { - if (ON_MHASH(crq)) - __cfq_del_crq_hash(crq); + hlist_del_init(&crq->hash); } static void cfq_remove_merge_hints(request_queue_t *q, struct cfq_rq *crq) @@ -120,32 +240,32 @@ static void cfq_remove_merge_hints(request_queue_t *q, struct cfq_rq *crq) if (q->last_merge == crq->request) q->last_merge = NULL; + + cfq_update_next_crq(crq); } static inline void cfq_add_crq_hash(struct cfq_data *cfqd, struct cfq_rq *crq) { - struct request *rq = crq->request; + const int hash_idx = CFQ_MHASH_FN(rq_hash_key(crq->request)); - BUG_ON(ON_MHASH(crq)); + BUG_ON(!hlist_unhashed(&crq->hash)); - list_add(&crq->hash, &cfqd->crq_hash[CFQ_MHASH_FN(rq_hash_key(rq))]); + hlist_add_head(&crq->hash, &cfqd->crq_hash[hash_idx]); } static struct request *cfq_find_rq_hash(struct cfq_data *cfqd, sector_t offset) { - struct list_head *hash_list = &cfqd->crq_hash[CFQ_MHASH_FN(offset)]; - struct list_head *entry, *next = hash_list->next; + struct hlist_head *hash_list = &cfqd->crq_hash[CFQ_MHASH_FN(offset)]; + struct hlist_node *entry, *next; - while ((entry = next) != hash_list) { + hlist_for_each_safe(entry, next, hash_list) { struct cfq_rq *crq = list_entry_hash(entry); struct request *__rq = crq->request; - next = entry->next; - - BUG_ON(!ON_MHASH(crq)); + BUG_ON(hlist_unhashed(&crq->hash)); if (!rq_mergeable(__rq)) { - __cfq_del_crq_hash(crq); + cfq_del_crq_hash(crq); continue; } @@ -157,29 +277,257 @@ static struct request *cfq_find_rq_hash(struct cfq_data *cfqd, sector_t offset) } /* - * rb tree support functions + * Lifted from AS - choose which of crq1 and crq2 that is best served now. + * We choose the request that is closest to the head right now. Distance + * behind the head are penalized and only allowed to a certain extent. */ -#define RB_NONE (2) -#define RB_EMPTY(node) ((node)->rb_node == NULL) -#define RB_CLEAR(node) ((node)->rb_color = RB_NONE) -#define RB_CLEAR_ROOT(root) ((root)->rb_node = NULL) -#define ON_RB(node) ((node)->rb_color != RB_NONE) -#define rb_entry_crq(node) rb_entry((node), struct cfq_rq, rb_node) -#define rq_rb_key(rq) (rq)->sector +static struct cfq_rq * +cfq_choose_req(struct cfq_data *cfqd, struct cfq_rq *crq1, struct cfq_rq *crq2) +{ + sector_t last, s1, s2, d1 = 0, d2 = 0; + int r1_wrap = 0, r2_wrap = 0; /* requests are behind the disk head */ + unsigned long back_max; + + if (crq1 == NULL || crq1 == crq2) + return crq2; + if (crq2 == NULL) + return crq1; + + s1 = crq1->request->sector; + s2 = crq2->request->sector; -static inline void cfq_del_crq_rb(struct cfq_queue *cfqq, struct cfq_rq *crq) + last = cfqd->last_sector; + +#if 0 + if (!list_empty(&cfqd->queue->queue_head)) { + struct list_head *entry = &cfqd->queue->queue_head; + unsigned long distance = ~0UL; + struct request *rq; + + while ((entry = entry->prev) != &cfqd->queue->queue_head) { + rq = list_entry_rq(entry); + + if (blk_barrier_rq(rq)) + break; + + if (distance < abs(s1 - rq->sector + rq->nr_sectors)) { + distance = abs(s1 - rq->sector +rq->nr_sectors); + last = rq->sector + rq->nr_sectors; + } + if (distance < abs(s2 - rq->sector + rq->nr_sectors)) { + distance = abs(s2 - rq->sector +rq->nr_sectors); + last = rq->sector + rq->nr_sectors; + } + } + } +#endif + + /* + * by definition, 1KiB is 2 sectors + */ + back_max = cfqd->cfq_back_max * 2; + + /* + * Strict one way elevator _except_ in the case where we allow + * short backward seeks which are biased as twice the cost of a + * similar forward seek. + */ + if (s1 >= last) + d1 = s1 - last; + else if (s1 + back_max >= last) + d1 = (last - s1) * cfqd->cfq_back_penalty; + else + r1_wrap = 1; + + if (s2 >= last) + d2 = s2 - last; + else if (s2 + back_max >= last) + d2 = (last - s2) * cfqd->cfq_back_penalty; + else + r2_wrap = 1; + + /* Found required data */ + if (!r1_wrap && r2_wrap) + return crq1; + else if (!r2_wrap && r1_wrap) + return crq2; + else if (r1_wrap && r2_wrap) { + /* both behind the head */ + if (s1 <= s2) + return crq1; + else + return crq2; + } + + /* Both requests in front of the head */ + if (d1 < d2) + return crq1; + else if (d2 < d1) + return crq2; + else { + if (s1 >= s2) + return crq1; + else + return crq2; + } +} + +/* + * would be nice to take fifo expire time into account as well + */ +static struct cfq_rq * +cfq_find_next_crq(struct cfq_data *cfqd, struct cfq_queue *cfqq, + struct cfq_rq *last) { + struct cfq_rq *crq_next = NULL, *crq_prev = NULL; + struct rb_node *rbnext, *rbprev; + + if (!ON_RB(&last->rb_node)) + return NULL; + + if ((rbnext = rb_next(&last->rb_node)) == NULL) + rbnext = rb_first(&cfqq->sort_list); + + rbprev = rb_prev(&last->rb_node); + + if (rbprev) + crq_prev = rb_entry_crq(rbprev); + if (rbnext) + crq_next = rb_entry_crq(rbnext); + + return cfq_choose_req(cfqd, crq_next, crq_prev); +} + +static void cfq_update_next_crq(struct cfq_rq *crq) +{ + struct cfq_queue *cfqq = crq->cfq_queue; + + if (cfqq->next_crq == crq) + cfqq->next_crq = cfq_find_next_crq(cfqq->cfqd, cfqq, crq); +} + +static int cfq_check_sort_rr_list(struct cfq_queue *cfqq) +{ + struct list_head *head = &cfqq->cfqd->rr_list; + struct list_head *next, *prev; + + /* + * list might still be ordered + */ + next = cfqq->cfq_list.next; + if (next != head) { + struct cfq_queue *cnext = list_entry_cfqq(next); + + if (cfqq->service_used > cnext->service_used) + return 1; + } + + prev = cfqq->cfq_list.prev; + if (prev != head) { + struct cfq_queue *cprev = list_entry_cfqq(prev); + + if (cfqq->service_used < cprev->service_used) + return 1; + } + + return 0; +} + +static void cfq_sort_rr_list(struct cfq_queue *cfqq, int new_queue) +{ + struct list_head *entry = &cfqq->cfqd->rr_list; + + if (!cfqq->on_rr) + return; + if (!new_queue && !cfq_check_sort_rr_list(cfqq)) + return; + + list_del(&cfqq->cfq_list); + + /* + * sort by our mean service_used, sub-sort by in-flight requests + */ + while ((entry = entry->prev) != &cfqq->cfqd->rr_list) { + struct cfq_queue *__cfqq = list_entry_cfqq(entry); + + if (cfqq->service_used > __cfqq->service_used) + break; + else if (cfqq->service_used == __cfqq->service_used) { + struct list_head *prv; + + while ((prv = entry->prev) != &cfqq->cfqd->rr_list) { + __cfqq = list_entry_cfqq(prv); + + WARN_ON(__cfqq->service_used > cfqq->service_used); + if (cfqq->service_used != __cfqq->service_used) + break; + if (cfqq->in_flight > __cfqq->in_flight) + break; + + entry = prv; + } + } + } + + list_add(&cfqq->cfq_list, entry); +} + +/* + * add to busy list of queues for service, trying to be fair in ordering + * the pending list according to requests serviced + */ +static inline void +cfq_add_cfqq_rr(struct cfq_data *cfqd, struct cfq_queue *cfqq) +{ + /* + * it's currently on the empty list + */ + cfqq->on_rr = 1; + cfqd->busy_queues++; + + if (time_after(jiffies, cfqq->service_start + cfq_service)) + cfqq->service_used >>= 3; + + cfq_sort_rr_list(cfqq, 1); +} + +static inline void +cfq_del_cfqq_rr(struct cfq_data *cfqd, struct cfq_queue *cfqq) +{ + list_move(&cfqq->cfq_list, &cfqd->empty_list); + cfqq->on_rr = 0; + + BUG_ON(!cfqd->busy_queues); + cfqd->busy_queues--; +} + +/* + * rb tree support functions + */ +static inline void cfq_del_crq_rb(struct cfq_rq *crq) +{ + struct cfq_queue *cfqq = crq->cfq_queue; + if (ON_RB(&crq->rb_node)) { - cfqq->queued[rq_data_dir(crq->request)]--; + struct cfq_data *cfqd = cfqq->cfqd; + + BUG_ON(!cfqq->queued[crq->is_sync]); + + cfq_update_next_crq(crq); + + cfqq->queued[crq->is_sync]--; rb_erase(&crq->rb_node, &cfqq->sort_list); - crq->cfq_queue = NULL; + RB_CLEAR_COLOR(&crq->rb_node); + + if (RB_EMPTY(&cfqq->sort_list) && cfqq->on_rr) + cfq_del_cfqq_rr(cfqd, cfqq); } } static struct cfq_rq * -__cfq_add_crq_rb(struct cfq_queue *cfqq, struct cfq_rq *crq) +__cfq_add_crq_rb(struct cfq_rq *crq) { - struct rb_node **p = &cfqq->sort_list.rb_node; + struct rb_node **p = &crq->cfq_queue->sort_list.rb_node; struct rb_node *parent = NULL; struct cfq_rq *__crq; @@ -199,30 +547,50 @@ __cfq_add_crq_rb(struct cfq_queue *cfqq, struct cfq_rq *crq) return NULL; } -static void -cfq_add_crq_rb(struct cfq_data *cfqd, struct cfq_queue *cfqq,struct cfq_rq *crq) +static void cfq_add_crq_rb(struct cfq_rq *crq) { + struct cfq_queue *cfqq = crq->cfq_queue; + struct cfq_data *cfqd = cfqq->cfqd; struct request *rq = crq->request; struct cfq_rq *__alias; crq->rb_key = rq_rb_key(rq); - cfqq->queued[rq_data_dir(rq)]++; -retry: - __alias = __cfq_add_crq_rb(cfqq, crq); - if (!__alias) { - rb_insert_color(&crq->rb_node, &cfqq->sort_list); - crq->cfq_queue = cfqq; - return; + cfqq->queued[crq->is_sync]++; + + /* + * looks a little odd, but the first insert might return an alias. + * if that happens, put the alias on the dispatch list + */ + while ((__alias = __cfq_add_crq_rb(crq)) != NULL) + cfq_dispatch_sort(cfqd->queue, __alias); + + rb_insert_color(&crq->rb_node, &cfqq->sort_list); + + if (!cfqq->on_rr) + cfq_add_cfqq_rr(cfqd, cfqq); + + /* + * check if this request is a better next-serve candidate + */ + cfqq->next_crq = cfq_choose_req(cfqd, cfqq->next_crq, crq); +} + +static inline void +cfq_reposition_crq_rb(struct cfq_queue *cfqq, struct cfq_rq *crq) +{ + if (ON_RB(&crq->rb_node)) { + rb_erase(&crq->rb_node, &cfqq->sort_list); + cfqq->queued[crq->is_sync]--; } - cfq_dispatch_sort(cfqd, cfqq, __alias); - goto retry; + cfq_add_crq_rb(crq); } static struct request * cfq_find_rq_rb(struct cfq_data *cfqd, sector_t sector) { - struct cfq_queue *cfqq = cfq_find_cfq_hash(cfqd, current->tgid); + const unsigned long key = cfq_hash_key(cfqd, current); + struct cfq_queue *cfqq = cfq_find_cfq_hash(cfqd, key); struct rb_node *n; if (!cfqq) @@ -244,23 +612,37 @@ out: return NULL; } -static void cfq_remove_request(request_queue_t *q, struct request *rq) +/* + * make sure the service time gets corrected on reissue of this request + */ +static void cfq_requeue_request(request_queue_t *q, struct request *rq) { - struct cfq_data *cfqd = q->elevator->elevator_data; struct cfq_rq *crq = RQ_DATA(rq); if (crq) { struct cfq_queue *cfqq = crq->cfq_queue; + if (cfqq->cfqd->cfq_tagged) { + cfqq->service_used--; + cfq_sort_rr_list(cfqq, 0); + } + + crq->accounted = 0; + cfqq->cfqd->rq_in_driver--; + } + list_add(&rq->queuelist, &q->queue_head); +} + +static void cfq_remove_request(request_queue_t *q, struct request *rq) +{ + struct cfq_rq *crq = RQ_DATA(rq); + + if (crq) { cfq_remove_merge_hints(q, crq); list_del_init(&rq->queuelist); - if (cfqq) { - cfq_del_crq_rb(cfqq, crq); - - if (RB_EMPTY(&cfqq->sort_list)) - cfq_put_queue(cfqd, cfqq); - } + if (crq->cfq_queue) + cfq_del_crq_rb(crq); } } @@ -314,92 +696,240 @@ static void cfq_merged_request(request_queue_t *q, struct request *req) if (ON_RB(&crq->rb_node) && (rq_rb_key(req) != crq->rb_key)) { struct cfq_queue *cfqq = crq->cfq_queue; - cfq_del_crq_rb(cfqq, crq); - cfq_add_crq_rb(cfqd, cfqq, crq); + cfq_update_next_crq(crq); + cfq_reposition_crq_rb(cfqq, crq); } q->last_merge = req; } static void -cfq_merged_requests(request_queue_t *q, struct request *req, +cfq_merged_requests(request_queue_t *q, struct request *rq, struct request *next) { - cfq_merged_request(q, req); + struct cfq_rq *crq = RQ_DATA(rq); + struct cfq_rq *cnext = RQ_DATA(next); + + cfq_merged_request(q, rq); + + if (!list_empty(&rq->queuelist) && !list_empty(&next->queuelist)) { + if (time_before(cnext->queue_start, crq->queue_start)) { + list_move(&rq->queuelist, &next->queuelist); + crq->queue_start = cnext->queue_start; + } + } + + cfq_update_next_crq(cnext); cfq_remove_request(q, next); } -static void -cfq_dispatch_sort(struct cfq_data *cfqd, struct cfq_queue *cfqq, - struct cfq_rq *crq) +/* + * we dispatch cfqd->cfq_quantum requests in total from the rr_list queues, + * this function sector sorts the selected request to minimize seeks. we start + * at cfqd->last_sector, not 0. + */ +static void cfq_dispatch_sort(request_queue_t *q, struct cfq_rq *crq) { - struct list_head *head = cfqd->dispatch, *entry = head; + struct cfq_data *cfqd = q->elevator->elevator_data; + struct cfq_queue *cfqq = crq->cfq_queue; + struct list_head *head = &q->queue_head, *entry = head; struct request *__rq; + sector_t last; - cfq_del_crq_rb(cfqq, crq); - cfq_remove_merge_hints(cfqd->queue, crq); + cfq_del_crq_rb(crq); + cfq_remove_merge_hints(q, crq); + list_del(&crq->request->queuelist); - if (!list_empty(head)) { - __rq = list_entry_rq(head->next); + last = cfqd->last_sector; + while ((entry = entry->prev) != head) { + __rq = list_entry_rq(entry); - if (crq->request->sector < __rq->sector) { - entry = head->prev; - goto link; + if (blk_barrier_rq(crq->request)) + break; + if (!blk_fs_request(crq->request)) + break; + + if (crq->request->sector > __rq->sector) + break; + if (__rq->sector > last && crq->request->sector < last) { + last = crq->request->sector; + break; } } - while ((entry = entry->prev) != head) { - __rq = list_entry_rq(entry); + cfqd->last_sector = last; + crq->in_flight = 1; + cfqq->in_flight++; + list_add(&crq->request->queuelist, entry); +} - if (crq->request->sector <= __rq->sector) - break; +/* + * return expired entry, or NULL to just start from scratch in rbtree + */ +static inline struct cfq_rq *cfq_check_fifo(struct cfq_queue *cfqq) +{ + struct cfq_data *cfqd = cfqq->cfqd; + const int reads = !list_empty(&cfqq->fifo[0]); + const int writes = !list_empty(&cfqq->fifo[1]); + unsigned long now = jiffies; + struct cfq_rq *crq; + + if (time_before(now, cfqq->last_fifo_expire + cfqd->cfq_fifo_batch_expire)) + return NULL; + + crq = RQ_DATA(list_entry(cfqq->fifo[0].next, struct request, queuelist)); + if (reads && time_after(now, crq->queue_start + cfqd->cfq_fifo_expire_r)) { + cfqq->last_fifo_expire = now; + return crq; } -link: - list_add_tail(&crq->request->queuelist, entry); + crq = RQ_DATA(list_entry(cfqq->fifo[1].next, struct request, queuelist)); + if (writes && time_after(now, crq->queue_start + cfqd->cfq_fifo_expire_w)) { + cfqq->last_fifo_expire = now; + return crq; + } + + return NULL; } +/* + * dispatch a single request from given queue + */ static inline void -__cfq_dispatch_requests(request_queue_t *q, struct cfq_data *cfqd, - struct cfq_queue *cfqq) +cfq_dispatch_request(request_queue_t *q, struct cfq_data *cfqd, + struct cfq_queue *cfqq) { - struct cfq_rq *crq = rb_entry_crq(rb_first(&cfqq->sort_list)); + struct cfq_rq *crq; + + /* + * follow expired path, else get first next available + */ + if ((crq = cfq_check_fifo(cfqq)) == NULL) { + if (cfqd->find_best_crq) + crq = cfqq->next_crq; + else + crq = rb_entry_crq(rb_first(&cfqq->sort_list)); + } - cfq_dispatch_sort(cfqd, cfqq, crq); + cfqd->last_sector = crq->request->sector + crq->request->nr_sectors; + + /* + * finally, insert request into driver list + */ + cfq_dispatch_sort(q, crq); } -static int cfq_dispatch_requests(request_queue_t *q, struct cfq_data *cfqd) +static int cfq_dispatch_requests(request_queue_t *q, int max_dispatch) { + struct cfq_data *cfqd = q->elevator->elevator_data; struct cfq_queue *cfqq; struct list_head *entry, *tmp; - int ret, queued, good_queues; + int queued, busy_queues, first_round; if (list_empty(&cfqd->rr_list)) return 0; - queued = ret = 0; + queued = 0; + first_round = 1; restart: - good_queues = 0; + busy_queues = 0; list_for_each_safe(entry, tmp, &cfqd->rr_list) { - cfqq = list_entry_cfqq(cfqd->rr_list.next); + cfqq = list_entry_cfqq(entry); BUG_ON(RB_EMPTY(&cfqq->sort_list)); - __cfq_dispatch_requests(q, cfqd, cfqq); + /* + * first round of queueing, only select from queues that + * don't already have io in-flight + */ + if (first_round && cfqq->in_flight) + continue; + + cfq_dispatch_request(q, cfqd, cfqq); - if (RB_EMPTY(&cfqq->sort_list)) - cfq_put_queue(cfqd, cfqq); - else - good_queues++; + if (!RB_EMPTY(&cfqq->sort_list)) + busy_queues++; queued++; - ret = 1; } - if ((queued < cfqd->cfq_quantum) && good_queues) + if ((queued < max_dispatch) && (busy_queues || first_round)) { + first_round = 0; goto restart; + } - return ret; + return queued; +} + +static inline void cfq_account_dispatch(struct cfq_rq *crq) +{ + struct cfq_queue *cfqq = crq->cfq_queue; + struct cfq_data *cfqd = cfqq->cfqd; + unsigned long now, elapsed; + + /* + * accounted bit is necessary since some drivers will call + * elv_next_request() many times for the same request (eg ide) + */ + if (crq->accounted) + return; + + now = jiffies; + if (cfqq->service_start == ~0UL) + cfqq->service_start = now; + + /* + * on drives with tagged command queueing, command turn-around time + * doesn't necessarily reflect the time spent processing this very + * command inside the drive. so do the accounting differently there, + * by just sorting on the number of requests + */ + if (cfqd->cfq_tagged) { + if (time_after(now, cfqq->service_start + cfq_service)) { + cfqq->service_start = now; + cfqq->service_used /= 10; + } + + cfqq->service_used++; + cfq_sort_rr_list(cfqq, 0); + } + + elapsed = now - crq->queue_start; + if (elapsed > max_elapsed_dispatch) + max_elapsed_dispatch = elapsed; + + crq->accounted = 1; + crq->service_start = now; + + if (++cfqd->rq_in_driver >= CFQ_MAX_TAG && !cfqd->cfq_tagged) { + cfqq->cfqd->cfq_tagged = 1; + printk("cfq: depth %d reached, tagging now on\n", CFQ_MAX_TAG); + } +} + +static inline void +cfq_account_completion(struct cfq_queue *cfqq, struct cfq_rq *crq) +{ + struct cfq_data *cfqd = cfqq->cfqd; + + WARN_ON(!cfqd->rq_in_driver); + cfqd->rq_in_driver--; + + if (!cfqd->cfq_tagged) { + unsigned long now = jiffies; + unsigned long duration = now - crq->service_start; + + if (time_after(now, cfqq->service_start + cfq_service)) { + cfqq->service_start = now; + cfqq->service_used >>= 3; + } + + cfqq->service_used += duration; + cfq_sort_rr_list(cfqq, 0); + + if (duration > max_elapsed_crq) + max_elapsed_crq = duration; + } } static struct request *cfq_next_request(request_queue_t *q) @@ -407,100 +937,305 @@ static struct request *cfq_next_request(request_queue_t *q) struct cfq_data *cfqd = q->elevator->elevator_data; struct request *rq; - if (!list_empty(cfqd->dispatch)) { + if (!list_empty(&q->queue_head)) { struct cfq_rq *crq; dispatch: - rq = list_entry_rq(cfqd->dispatch->next); + rq = list_entry_rq(q->queue_head.next); - crq = RQ_DATA(rq); - if (crq) + if ((crq = RQ_DATA(rq)) != NULL) { cfq_remove_merge_hints(q, crq); + cfq_account_dispatch(crq); + } return rq; } - if (cfq_dispatch_requests(q, cfqd)) + if (cfq_dispatch_requests(q, cfqd->cfq_quantum)) goto dispatch; return NULL; } +/* + * task holds one reference to the queue, dropped when task exits. each crq + * in-flight on this queue also holds a reference, dropped when crq is freed. + * + * queue lock must be held here. + */ +static void cfq_put_queue(struct cfq_queue *cfqq) +{ + BUG_ON(!atomic_read(&cfqq->ref)); + + if (!atomic_dec_and_test(&cfqq->ref)) + return; + + BUG_ON(rb_first(&cfqq->sort_list)); + BUG_ON(cfqq->on_rr); + + cfq_put_cfqd(cfqq->cfqd); + + /* + * it's on the empty list and still hashed + */ + list_del(&cfqq->cfq_list); + hlist_del(&cfqq->cfq_hash); + kmem_cache_free(cfq_pool, cfqq); +} + static inline struct cfq_queue * -__cfq_find_cfq_hash(struct cfq_data *cfqd, int pid, const int hashval) +__cfq_find_cfq_hash(struct cfq_data *cfqd, unsigned long key, const int hashval) { - struct list_head *hash_list = &cfqd->cfq_hash[hashval]; - struct list_head *entry; + struct hlist_head *hash_list = &cfqd->cfq_hash[hashval]; + struct hlist_node *entry, *next; - list_for_each(entry, hash_list) { + hlist_for_each_safe(entry, next, hash_list) { struct cfq_queue *__cfqq = list_entry_qhash(entry); - if (__cfqq->pid == pid) + if (__cfqq->key == key) return __cfqq; } return NULL; } -static struct cfq_queue *cfq_find_cfq_hash(struct cfq_data *cfqd, int pid) +static struct cfq_queue * +cfq_find_cfq_hash(struct cfq_data *cfqd, unsigned long key) +{ + return __cfq_find_cfq_hash(cfqd, key, hash_long(key, CFQ_QHASH_SHIFT)); +} + +static inline void +cfq_rehash_cfqq(struct cfq_data *cfqd, struct cfq_queue **cfqq, + struct cfq_io_context *cic) +{ + unsigned long hashkey = cfq_hash_key(cfqd, current); + unsigned long hashval = hash_long(hashkey, CFQ_QHASH_SHIFT); + struct cfq_queue *__cfqq; + unsigned long flags; + + spin_lock_irqsave(cfqd->queue->queue_lock, flags); + + hlist_del(&(*cfqq)->cfq_hash); + + __cfqq = __cfq_find_cfq_hash(cfqd, hashkey, hashval); + if (!__cfqq || __cfqq == *cfqq) { + __cfqq = *cfqq; + hlist_add_head(&__cfqq->cfq_hash, &cfqd->cfq_hash[hashval]); + __cfqq->key_type = cfqd->key_type; + } else { + atomic_inc(&__cfqq->ref); + cic->cfqq = __cfqq; + cfq_put_queue(*cfqq); + *cfqq = __cfqq; + } + + cic->cfqq = __cfqq; + spin_unlock_irqrestore(cfqd->queue->queue_lock, flags); +} + +static void cfq_free_io_context(struct cfq_io_context *cic) { - const int hashval = hash_long(current->tgid, CFQ_QHASH_SHIFT); + kmem_cache_free(cfq_ioc_pool, cic); +} - return __cfq_find_cfq_hash(cfqd, pid, hashval); +/* + * locking hierarchy is: io_context lock -> queue locks + */ +static void cfq_exit_io_context(struct cfq_io_context *cic) +{ + struct cfq_queue *cfqq = cic->cfqq; + struct list_head *entry = &cic->list; + request_queue_t *q; + unsigned long flags; + + /* + * put the reference this task is holding to the various queues + */ + spin_lock_irqsave(&cic->ioc->lock, flags); + while ((entry = cic->list.next) != &cic->list) { + struct cfq_io_context *__cic; + + __cic = list_entry(entry, struct cfq_io_context, list); + list_del(entry); + + q = __cic->cfqq->cfqd->queue; + spin_lock(q->queue_lock); + cfq_put_queue(__cic->cfqq); + spin_unlock(q->queue_lock); + } + + q = cfqq->cfqd->queue; + spin_lock(q->queue_lock); + cfq_put_queue(cfqq); + spin_unlock(q->queue_lock); + + cic->cfqq = NULL; + spin_unlock_irqrestore(&cic->ioc->lock, flags); } -static void cfq_put_queue(struct cfq_data *cfqd, struct cfq_queue *cfqq) +static struct cfq_io_context *cfq_alloc_io_context(int gfp_flags) { - cfqd->busy_queues--; - list_del(&cfqq->cfq_list); - list_del(&cfqq->cfq_hash); - mempool_free(cfqq, cfq_mpool); + struct cfq_io_context *cic = kmem_cache_alloc(cfq_ioc_pool, gfp_flags); + + if (cic) { + cic->dtor = cfq_free_io_context; + cic->exit = cfq_exit_io_context; + INIT_LIST_HEAD(&cic->list); + cic->cfqq = NULL; + } + + return cic; } -static struct cfq_queue *__cfq_get_queue(struct cfq_data *cfqd, int pid, - int gfp_mask) +/* + * Setup general io context and cfq io context. There can be several cfq + * io contexts per general io context, if this process is doing io to more + * than one device managed by cfq. Note that caller is holding a reference to + * cfqq, so we don't need to worry about it disappearing + */ +static struct cfq_io_context * +cfq_get_io_context(struct cfq_queue **cfqq, int gfp_flags) { - const int hashval = hash_long(current->tgid, CFQ_QHASH_SHIFT); + struct cfq_data *cfqd = (*cfqq)->cfqd; + struct cfq_queue *__cfqq = *cfqq; + struct cfq_io_context *cic; + struct io_context *ioc; + + might_sleep_if(gfp_flags & __GFP_WAIT); + + ioc = get_io_context(gfp_flags); + if (!ioc) + return NULL; + + if ((cic = ioc->cic) == NULL) { + cic = cfq_alloc_io_context(gfp_flags); + + if (cic == NULL) + goto err; + + ioc->cic = cic; + cic->ioc = ioc; + cic->cfqq = __cfqq; + atomic_inc(&__cfqq->ref); + } else { + struct cfq_io_context *__cic; + unsigned long flags; + + /* + * since the first cic on the list is actually the head + * itself, need to check this here or we'll duplicate an + * cic per ioc for no reason + */ + if (cic->cfqq == __cfqq) + goto out; + + /* + * cic exists, check if we already are there. linear search + * should be ok here, the list will usually not be more than + * 1 or a few entries long + */ + spin_lock_irqsave(&ioc->lock, flags); + list_for_each_entry(__cic, &cic->list, list) { + /* + * this process is already holding a reference to + * this queue, so no need to get one more + */ + if (__cic->cfqq == __cfqq) { + cic = __cic; + spin_unlock_irqrestore(&ioc->lock, flags); + goto out; + } + } + spin_unlock_irqrestore(&ioc->lock, flags); + + /* + * nope, process doesn't have a cic assoicated with this + * cfqq yet. get a new one and add to list + */ + __cic = cfq_alloc_io_context(gfp_flags); + if (__cic == NULL) + goto err; + + __cic->ioc = ioc; + __cic->cfqq = __cfqq; + atomic_inc(&__cfqq->ref); + spin_lock_irqsave(&ioc->lock, flags); + list_add(&__cic->list, &cic->list); + spin_unlock_irqrestore(&ioc->lock, flags); + + cic = __cic; + *cfqq = __cfqq; + } + +out: + /* + * if key_type has been changed on the fly, we lazily rehash + * each queue at lookup time + */ + if ((*cfqq)->key_type != cfqd->key_type) + cfq_rehash_cfqq(cfqd, cfqq, cic); + + return cic; +err: + put_io_context(ioc); + return NULL; +} + +static struct cfq_queue * +__cfq_get_queue(struct cfq_data *cfqd, unsigned long key, int gfp_mask) +{ + const int hashval = hash_long(key, CFQ_QHASH_SHIFT); struct cfq_queue *cfqq, *new_cfqq = NULL; - request_queue_t *q = cfqd->queue; retry: - cfqq = __cfq_find_cfq_hash(cfqd, pid, hashval); + cfqq = __cfq_find_cfq_hash(cfqd, key, hashval); if (!cfqq) { if (new_cfqq) { cfqq = new_cfqq; new_cfqq = NULL; } else if (gfp_mask & __GFP_WAIT) { - spin_unlock_irq(q->queue_lock); - new_cfqq = mempool_alloc(cfq_mpool, gfp_mask); - spin_lock_irq(q->queue_lock); + spin_unlock_irq(cfqd->queue->queue_lock); + new_cfqq = kmem_cache_alloc(cfq_pool, gfp_mask); + spin_lock_irq(cfqd->queue->queue_lock); goto retry; } else - return NULL; + goto out; + + memset(cfqq, 0, sizeof(*cfqq)); - INIT_LIST_HEAD(&cfqq->cfq_hash); + INIT_HLIST_NODE(&cfqq->cfq_hash); INIT_LIST_HEAD(&cfqq->cfq_list); RB_CLEAR_ROOT(&cfqq->sort_list); - - cfqq->pid = pid; - cfqq->queued[0] = cfqq->queued[1] = 0; - list_add(&cfqq->cfq_hash, &cfqd->cfq_hash[hashval]); + INIT_LIST_HEAD(&cfqq->fifo[0]); + INIT_LIST_HEAD(&cfqq->fifo[1]); + + cfqq->key = key; + hlist_add_head(&cfqq->cfq_hash, &cfqd->cfq_hash[hashval]); + atomic_set(&cfqq->ref, 0); + cfqq->cfqd = cfqd; + atomic_inc(&cfqd->ref); + cfqq->key_type = cfqd->key_type; + cfqq->service_start = ~0UL; } if (new_cfqq) - mempool_free(new_cfqq, cfq_mpool); + kmem_cache_free(cfq_pool, new_cfqq); + atomic_inc(&cfqq->ref); +out: + WARN_ON((gfp_mask & __GFP_WAIT) && !cfqq); return cfqq; } -static struct cfq_queue *cfq_get_queue(struct cfq_data *cfqd, int pid, - int gfp_mask) +static struct cfq_queue * +cfq_get_queue(struct cfq_data *cfqd, unsigned long key, int gfp_mask) { request_queue_t *q = cfqd->queue; struct cfq_queue *cfqq; spin_lock_irq(q->queue_lock); - cfqq = __cfq_get_queue(cfqd, pid, gfp_mask); + cfqq = __cfq_get_queue(cfqd, key, gfp_mask); spin_unlock_irq(q->queue_lock); return cfqq; @@ -508,24 +1243,14 @@ static struct cfq_queue *cfq_get_queue(struct cfq_data *cfqd, int pid, static void cfq_enqueue(struct cfq_data *cfqd, struct cfq_rq *crq) { - struct cfq_queue *cfqq; + crq->is_sync = 0; + if (rq_data_dir(crq->request) == READ || current->flags & PF_SYNCWRITE) + crq->is_sync = 1; - cfqq = __cfq_get_queue(cfqd, current->tgid, GFP_ATOMIC); - if (cfqq) { - cfq_add_crq_rb(cfqd, cfqq, crq); + cfq_add_crq_rb(crq); + crq->queue_start = jiffies; - if (list_empty(&cfqq->cfq_list)) { - list_add(&cfqq->cfq_list, &cfqd->rr_list); - cfqd->busy_queues++; - } - } else { - /* - * should can only happen if the request wasn't allocated - * through blk_alloc_request(), eg stack requests from ide-cd - * (those should be removed) _and_ we are in OOM. - */ - list_add_tail(&crq->request->queuelist, cfqd->dispatch); - } + list_add_tail(&crq->request->queuelist, &crq->cfq_queue->fifo[crq->is_sync]); } static void @@ -536,12 +1261,12 @@ cfq_insert_request(request_queue_t *q, struct request *rq, int where) switch (where) { case ELEVATOR_INSERT_BACK: - while (cfq_dispatch_requests(q, cfqd)) + while (cfq_dispatch_requests(q, cfqd->cfq_quantum)) ; - list_add_tail(&rq->queuelist, cfqd->dispatch); + list_add_tail(&rq->queuelist, &q->queue_head); break; case ELEVATOR_INSERT_FRONT: - list_add(&rq->queuelist, cfqd->dispatch); + list_add(&rq->queuelist, &q->queue_head); break; case ELEVATOR_INSERT_SORT: BUG_ON(!blk_fs_request(rq)); @@ -564,10 +1289,25 @@ static int cfq_queue_empty(request_queue_t *q) { struct cfq_data *cfqd = q->elevator->elevator_data; - if (list_empty(cfqd->dispatch) && list_empty(&cfqd->rr_list)) - return 1; + return list_empty(&q->queue_head) && list_empty(&cfqd->rr_list); +} + +static void cfq_completed_request(request_queue_t *q, struct request *rq) +{ + struct cfq_rq *crq = RQ_DATA(rq); + + if (unlikely(!blk_fs_request(rq))) + return; + + if (crq->in_flight) { + struct cfq_queue *cfqq = crq->cfq_queue; + + WARN_ON(!cfqq->in_flight); + cfqq->in_flight--; + + cfq_account_completion(cfqq, crq); + } - return 0; } static struct request * @@ -598,90 +1338,167 @@ static int cfq_may_queue(request_queue_t *q, int rw) { struct cfq_data *cfqd = q->elevator->elevator_data; struct cfq_queue *cfqq; - int ret = 1; + int ret = ELV_MQUEUE_MAY; - if (!cfqd->busy_queues) - goto out; + if (current->flags & PF_MEMALLOC) + return ELV_MQUEUE_MAY; - cfqq = cfq_find_cfq_hash(cfqd, current->tgid); + cfqq = cfq_find_cfq_hash(cfqd, cfq_hash_key(cfqd, current)); if (cfqq) { - int limit = (q->nr_requests - cfqd->cfq_queued) / cfqd->busy_queues; + int limit = cfqd->max_queued; - if (limit < 3) - limit = 3; + if (cfqq->allocated[rw] < cfqd->cfq_queued) + return ELV_MQUEUE_MUST; + + if (cfqd->busy_queues) + limit = q->nr_requests / cfqd->busy_queues; + + if (limit < cfqd->cfq_queued) + limit = cfqd->cfq_queued; else if (limit > cfqd->max_queued) limit = cfqd->max_queued; - if (cfqq->queued[rw] > limit) - ret = 0; + if (cfqq->allocated[rw] >= limit) { + if (limit > cfqq->alloc_limit[rw]) + cfqq->alloc_limit[rw] = limit; + + ret = ELV_MQUEUE_NO; + } } -out: + return ret; } +static void cfq_check_waiters(request_queue_t *q, struct cfq_queue *cfqq) +{ + struct request_list *rl = &q->rq; + const int write = waitqueue_active(&rl->wait[WRITE]); + const int read = waitqueue_active(&rl->wait[READ]); + + if (read && cfqq->allocated[READ] < cfqq->alloc_limit[READ]) + wake_up(&rl->wait[READ]); + if (write && cfqq->allocated[WRITE] < cfqq->alloc_limit[WRITE]) + wake_up(&rl->wait[WRITE]); +} + +/* + * queue lock held here + */ static void cfq_put_request(request_queue_t *q, struct request *rq) { struct cfq_data *cfqd = q->elevator->elevator_data; struct cfq_rq *crq = RQ_DATA(rq); - struct request_list *rl; - int other_rw; if (crq) { + struct cfq_queue *cfqq = crq->cfq_queue; + BUG_ON(q->last_merge == rq); - BUG_ON(ON_MHASH(crq)); + BUG_ON(!hlist_unhashed(&crq->hash)); + + if (crq->io_context) + put_io_context(crq->io_context->ioc); + + if (!cfqq->allocated[crq->is_write]) { + WARN_ON(1); + cfqq->allocated[crq->is_write] = 1; + } + cfqq->allocated[crq->is_write]--; mempool_free(crq, cfqd->crq_pool); rq->elevator_private = NULL; - } - /* - * work-around for may_queue "bug": if a read gets issued and refused - * to queue because writes ate all the allowed slots and no other - * reads are pending for this queue, it could get stuck infinitely - * since freed_request() only checks the waitqueue for writes when - * freeing them. or vice versa for a single write vs many reads. - * so check here whether "the other" data direction might be able - * to queue and wake them - */ - rl = &q->rq; - other_rw = rq_data_dir(rq) ^ 1; - if (rl->count[other_rw] <= q->nr_requests) { smp_mb(); - if (waitqueue_active(&rl->wait[other_rw])) - wake_up(&rl->wait[other_rw]); + cfq_check_waiters(q, cfqq); + cfq_put_queue(cfqq); } } +/* + * Allocate cfq data structures associated with this request. A queue and + */ static int cfq_set_request(request_queue_t *q, struct request *rq, int gfp_mask) { struct cfq_data *cfqd = q->elevator->elevator_data; + struct cfq_io_context *cic; + const int rw = rq_data_dir(rq); struct cfq_queue *cfqq; struct cfq_rq *crq; + unsigned long flags; + + might_sleep_if(gfp_mask & __GFP_WAIT); + + spin_lock_irqsave(q->queue_lock, flags); + + cfqq = __cfq_get_queue(cfqd, cfq_hash_key(cfqd, current), gfp_mask); + if (!cfqq) { +#if 0 + cfqq = cfq_get_queue(cfqd, CFQ_KEY_SPARE, gfp_mask); + printk("%s: got spare queue\n", current->comm); +#else + goto out_lock; +#endif + } + + if (cfqq->allocated[rw] >= cfqd->max_queued) + goto out_lock; + + spin_unlock_irqrestore(q->queue_lock, flags); /* - * prepare a queue up front, so cfq_enqueue() doesn't have to + * if hashing type has changed, the cfq_queue might change here. we + * don't bother rechecking ->allocated since it should be a rare + * event */ - cfqq = cfq_get_queue(cfqd, current->tgid, gfp_mask); - if (!cfqq) - return 1; + cic = cfq_get_io_context(&cfqq, gfp_mask); + if (!cic) + goto err; crq = mempool_alloc(cfqd->crq_pool, gfp_mask); if (crq) { - memset(crq, 0, sizeof(*crq)); RB_CLEAR(&crq->rb_node); + crq->rb_key = 0; crq->request = rq; - crq->cfq_queue = NULL; - INIT_LIST_HEAD(&crq->hash); + INIT_HLIST_NODE(&crq->hash); + crq->cfq_queue = cfqq; + crq->io_context = cic; + crq->service_start = crq->queue_start = 0; + crq->in_flight = crq->accounted = crq->is_sync = 0; + crq->is_write = rw; rq->elevator_private = crq; + cfqq->allocated[rw]++; + cfqq->alloc_limit[rw] = 0; return 0; } + put_io_context(cic->ioc); +err: + spin_lock_irqsave(q->queue_lock, flags); + cfq_put_queue(cfqq); +out_lock: + spin_unlock_irqrestore(q->queue_lock, flags); return 1; } -static void cfq_exit_queue(elevator_t *e) +static void cfq_put_cfqd(struct cfq_data *cfqd) { - struct cfq_data *cfqd = e->elevator_data; + request_queue_t *q = cfqd->queue; + elevator_t *e = q->elevator; + struct cfq_queue *cfqq; + + if (!atomic_dec_and_test(&cfqd->ref)) + return; + + /* + * kill spare queue, getting it means we have two refences to it. + * drop both + */ + spin_lock_irq(q->queue_lock); + cfqq = __cfq_get_queue(cfqd, CFQ_KEY_SPARE, GFP_ATOMIC); + cfq_put_queue(cfqq); + cfq_put_queue(cfqq); + spin_unlock_irq(q->queue_lock); + + blk_put_queue(q); e->elevator_data = NULL; mempool_destroy(cfqd->crq_pool); @@ -690,9 +1507,15 @@ static void cfq_exit_queue(elevator_t *e) kfree(cfqd); } +static void cfq_exit_queue(elevator_t *e) +{ + cfq_put_cfqd(e->elevator_data); +} + static int cfq_init_queue(request_queue_t *q, elevator_t *e) { struct cfq_data *cfqd; + struct cfq_queue *cfqq; int i; cfqd = kmalloc(sizeof(*cfqd), GFP_KERNEL); @@ -701,12 +1524,13 @@ static int cfq_init_queue(request_queue_t *q, elevator_t *e) memset(cfqd, 0, sizeof(*cfqd)); INIT_LIST_HEAD(&cfqd->rr_list); + INIT_LIST_HEAD(&cfqd->empty_list); - cfqd->crq_hash = kmalloc(sizeof(struct list_head) * CFQ_MHASH_ENTRIES, GFP_KERNEL); + cfqd->crq_hash = kmalloc(sizeof(struct hlist_head) * CFQ_MHASH_ENTRIES, GFP_KERNEL); if (!cfqd->crq_hash) goto out_crqhash; - cfqd->cfq_hash = kmalloc(sizeof(struct list_head) * CFQ_QHASH_ENTRIES, GFP_KERNEL); + cfqd->cfq_hash = kmalloc(sizeof(struct hlist_head) * CFQ_QHASH_ENTRIES, GFP_KERNEL); if (!cfqd->cfq_hash) goto out_cfqhash; @@ -715,24 +1539,44 @@ static int cfq_init_queue(request_queue_t *q, elevator_t *e) goto out_crqpool; for (i = 0; i < CFQ_MHASH_ENTRIES; i++) - INIT_LIST_HEAD(&cfqd->crq_hash[i]); + INIT_HLIST_HEAD(&cfqd->crq_hash[i]); for (i = 0; i < CFQ_QHASH_ENTRIES; i++) - INIT_LIST_HEAD(&cfqd->cfq_hash[i]); + INIT_HLIST_HEAD(&cfqd->cfq_hash[i]); - cfqd->dispatch = &q->queue_head; e->elevator_data = cfqd; + cfqd->queue = q; + atomic_inc(&q->refcnt); + + /* + * setup spare failure queue + */ + cfqq = cfq_get_queue(cfqd, CFQ_KEY_SPARE, GFP_KERNEL); + if (!cfqq) + goto out_spare; /* * just set it to some high value, we want anyone to be able to queue * some requests. fairness is handled differently */ - cfqd->max_queued = q->nr_requests; - q->nr_requests = 8192; + q->nr_requests = 1024; + cfqd->max_queued = q->nr_requests / 16; + q->nr_batching = cfq_queued; + cfqd->key_type = CFQ_KEY_TGID; + cfqd->find_best_crq = 1; + atomic_set(&cfqd->ref, 1); cfqd->cfq_queued = cfq_queued; cfqd->cfq_quantum = cfq_quantum; + cfqd->cfq_fifo_expire_r = cfq_fifo_expire_r; + cfqd->cfq_fifo_expire_w = cfq_fifo_expire_w; + cfqd->cfq_fifo_batch_expire = cfq_fifo_rate; + cfqd->cfq_back_max = cfq_back_max; + cfqd->cfq_back_penalty = cfq_back_penalty; + return 0; +out_spare: + mempool_destroy(cfqd->crq_pool); out_crqpool: kfree(cfqd->cfq_hash); out_cfqhash: @@ -746,13 +1590,13 @@ static void cfq_slab_kill(void) { if (crq_pool) kmem_cache_destroy(crq_pool); - if (cfq_mpool) - mempool_destroy(cfq_mpool); if (cfq_pool) kmem_cache_destroy(cfq_pool); + if (cfq_ioc_pool) + kmem_cache_destroy(cfq_ioc_pool); } -static int cfq_slab_setup(void) +static int __init cfq_slab_setup(void) { crq_pool = kmem_cache_create("crq_pool", sizeof(struct cfq_rq), 0, 0, NULL, NULL); @@ -764,8 +1608,9 @@ static int cfq_slab_setup(void) if (!cfq_pool) goto fail; - cfq_mpool = mempool_create(64, mempool_alloc_slab, mempool_free_slab, cfq_pool); - if (!cfq_mpool) + cfq_ioc_pool = kmem_cache_create("cfq_ioc_pool", + sizeof(struct cfq_io_context), 0, 0, NULL, NULL); + if (!cfq_ioc_pool) goto fail; return 0; @@ -774,6 +1619,7 @@ fail: return -ENOMEM; } + /* * sysfs parts below --> */ @@ -798,6 +1644,94 @@ cfq_var_store(unsigned int *var, const char *page, size_t count) return count; } +static ssize_t +cfq_clear_elapsed(struct cfq_data *cfqd, const char *page, size_t count) +{ + max_elapsed_dispatch = max_elapsed_crq = 0; + return count; +} + +static ssize_t +cfq_set_key_type(struct cfq_data *cfqd, const char *page, size_t count) +{ + spin_lock_irq(cfqd->queue->queue_lock); + if (!strncmp(page, "pgid", 4)) + cfqd->key_type = CFQ_KEY_PGID; + else if (!strncmp(page, "tgid", 4)) + cfqd->key_type = CFQ_KEY_TGID; + else if (!strncmp(page, "uid", 3)) + cfqd->key_type = CFQ_KEY_UID; + else if (!strncmp(page, "gid", 3)) + cfqd->key_type = CFQ_KEY_GID; + spin_unlock_irq(cfqd->queue->queue_lock); + return count; +} + +static ssize_t +cfq_read_key_type(struct cfq_data *cfqd, char *page) +{ + ssize_t len = 0; + int i; + + for (i = CFQ_KEY_PGID; i < CFQ_KEY_LAST; i++) { + if (cfqd->key_type == i) + len += sprintf(page+len, "[%s] ", cfq_key_types[i]); + else + len += sprintf(page+len, "%s ", cfq_key_types[i]); + } + len += sprintf(page+len, "\n"); + return len; +} + +static ssize_t +cfq_status_show(struct cfq_data *cfqd, char *page) +{ + struct list_head *entry; + struct cfq_queue *cfqq; + ssize_t len; + int i = 0, queues; + + len = sprintf(page, "Busy queues: %u\n", cfqd->busy_queues); + len += sprintf(page+len, "key type: %s\n", + cfq_key_types[cfqd->key_type]); + len += sprintf(page+len, "last sector: %Lu\n", + (unsigned long long)cfqd->last_sector); + len += sprintf(page+len, "max time in iosched: %lu\n", + max_elapsed_dispatch); + len += sprintf(page+len, "max completion time: %lu\n", max_elapsed_crq); + + len += sprintf(page+len, "Busy queue list:\n"); + spin_lock_irq(cfqd->queue->queue_lock); + list_for_each(entry, &cfqd->rr_list) { + i++; + cfqq = list_entry_cfqq(entry); + len += sprintf(page+len, " cfqq: key=%lu alloc=%d/%d, " + "queued=%d/%d, last_fifo=%lu, service_used=%lu\n", + cfqq->key, cfqq->allocated[0], cfqq->allocated[1], + cfqq->queued[0], cfqq->queued[1], + cfqq->last_fifo_expire, cfqq->service_used); + } + len += sprintf(page+len, " busy queues total: %d\n", i); + queues = i; + + len += sprintf(page+len, "Empty queue list:\n"); + i = 0; + list_for_each(entry, &cfqd->empty_list) { + i++; + cfqq = list_entry_cfqq(entry); + len += sprintf(page+len, " cfqq: key=%lu alloc=%d/%d, " + "queued=%d/%d, last_fifo=%lu, service_used=%lu\n", + cfqq->key, cfqq->allocated[0], cfqq->allocated[1], + cfqq->queued[0], cfqq->queued[1], + cfqq->last_fifo_expire, cfqq->service_used); + } + len += sprintf(page+len, " empty queues total: %d\n", i); + queues += i; + len += sprintf(page+len, "Total queues: %d\n", queues); + spin_unlock_irq(cfqd->queue->queue_lock); + return len; +} + #define SHOW_FUNCTION(__FUNC, __VAR) \ static ssize_t __FUNC(struct cfq_data *cfqd, char *page) \ { \ @@ -805,6 +1739,12 @@ static ssize_t __FUNC(struct cfq_data *cfqd, char *page) \ } SHOW_FUNCTION(cfq_quantum_show, cfqd->cfq_quantum); SHOW_FUNCTION(cfq_queued_show, cfqd->cfq_queued); +SHOW_FUNCTION(cfq_fifo_expire_r_show, cfqd->cfq_fifo_expire_r); +SHOW_FUNCTION(cfq_fifo_expire_w_show, cfqd->cfq_fifo_expire_w); +SHOW_FUNCTION(cfq_fifo_batch_expire_show, cfqd->cfq_fifo_batch_expire); +SHOW_FUNCTION(cfq_find_best_show, cfqd->find_best_crq); +SHOW_FUNCTION(cfq_back_max_show, cfqd->cfq_back_max); +SHOW_FUNCTION(cfq_back_penalty_show, cfqd->cfq_back_penalty); #undef SHOW_FUNCTION #define STORE_FUNCTION(__FUNC, __PTR, MIN, MAX) \ @@ -817,8 +1757,14 @@ static ssize_t __FUNC(struct cfq_data *cfqd, const char *page, size_t count) \ *(__PTR) = (MAX); \ return ret; \ } -STORE_FUNCTION(cfq_quantum_store, &cfqd->cfq_quantum, 1, INT_MAX); -STORE_FUNCTION(cfq_queued_store, &cfqd->cfq_queued, 1, INT_MAX); +STORE_FUNCTION(cfq_quantum_store, &cfqd->cfq_quantum, 1, UINT_MAX); +STORE_FUNCTION(cfq_queued_store, &cfqd->cfq_queued, 1, UINT_MAX); +STORE_FUNCTION(cfq_fifo_expire_r_store, &cfqd->cfq_fifo_expire_r, 1, UINT_MAX); +STORE_FUNCTION(cfq_fifo_expire_w_store, &cfqd->cfq_fifo_expire_w, 1, UINT_MAX); +STORE_FUNCTION(cfq_fifo_batch_expire_store, &cfqd->cfq_fifo_batch_expire, 0, UINT_MAX); +STORE_FUNCTION(cfq_find_best_store, &cfqd->find_best_crq, 0, 1); +STORE_FUNCTION(cfq_back_max_store, &cfqd->cfq_back_max, 0, UINT_MAX); +STORE_FUNCTION(cfq_back_penalty_store, &cfqd->cfq_back_penalty, 1, UINT_MAX); #undef STORE_FUNCTION static struct cfq_fs_entry cfq_quantum_entry = { @@ -831,10 +1777,62 @@ static struct cfq_fs_entry cfq_queued_entry = { .show = cfq_queued_show, .store = cfq_queued_store, }; +static struct cfq_fs_entry cfq_fifo_expire_r_entry = { + .attr = {.name = "fifo_expire_sync", .mode = S_IRUGO | S_IWUSR }, + .show = cfq_fifo_expire_r_show, + .store = cfq_fifo_expire_r_store, +}; +static struct cfq_fs_entry cfq_fifo_expire_w_entry = { + .attr = {.name = "fifo_expire_async", .mode = S_IRUGO | S_IWUSR }, + .show = cfq_fifo_expire_w_show, + .store = cfq_fifo_expire_w_store, +}; +static struct cfq_fs_entry cfq_fifo_batch_expire_entry = { + .attr = {.name = "fifo_batch_expire", .mode = S_IRUGO | S_IWUSR }, + .show = cfq_fifo_batch_expire_show, + .store = cfq_fifo_batch_expire_store, +}; +static struct cfq_fs_entry cfq_find_best_entry = { + .attr = {.name = "find_best_crq", .mode = S_IRUGO | S_IWUSR }, + .show = cfq_find_best_show, + .store = cfq_find_best_store, +}; +static struct cfq_fs_entry cfq_back_max_entry = { + .attr = {.name = "back_seek_max", .mode = S_IRUGO | S_IWUSR }, + .show = cfq_back_max_show, + .store = cfq_back_max_store, +}; +static struct cfq_fs_entry cfq_back_penalty_entry = { + .attr = {.name = "back_seek_penalty", .mode = S_IRUGO | S_IWUSR }, + .show = cfq_back_penalty_show, + .store = cfq_back_penalty_store, +}; +static struct cfq_fs_entry cfq_clear_elapsed_entry = { + .attr = {.name = "clear_elapsed", .mode = S_IWUSR }, + .store = cfq_clear_elapsed, +}; +static struct cfq_fs_entry cfq_misc_entry = { + .attr = {.name = "show_status", .mode = S_IRUGO }, + .show = cfq_status_show, +}; +static struct cfq_fs_entry cfq_key_type_entry = { + .attr = {.name = "key_type", .mode = S_IRUGO | S_IWUSR }, + .show = cfq_read_key_type, + .store = cfq_set_key_type, +}; static struct attribute *default_attrs[] = { &cfq_quantum_entry.attr, &cfq_queued_entry.attr, + &cfq_fifo_expire_r_entry.attr, + &cfq_fifo_expire_w_entry.attr, + &cfq_fifo_batch_expire_entry.attr, + &cfq_key_type_entry.attr, + &cfq_find_best_entry.attr, + &cfq_back_max_entry.attr, + &cfq_back_penalty_entry.attr, + &cfq_clear_elapsed_entry.attr, + &cfq_misc_entry.attr, NULL, }; @@ -883,7 +1881,9 @@ static struct elevator_type iosched_cfq = { .elevator_next_req_fn = cfq_next_request, .elevator_add_req_fn = cfq_insert_request, .elevator_remove_req_fn = cfq_remove_request, + .elevator_requeue_req_fn = cfq_requeue_request, .elevator_queue_empty_fn = cfq_queue_empty, + .elevator_completed_req_fn = cfq_completed_request, .elevator_former_req_fn = cfq_former_request, .elevator_latter_req_fn = cfq_latter_request, .elevator_set_req_fn = cfq_set_request, @@ -892,9 +1892,9 @@ static struct elevator_type iosched_cfq = { .elevator_init_fn = cfq_init_queue, .elevator_exit_fn = cfq_exit_queue, }, - .elevator_ktype = &cfq_ktype, - .elevator_name = "cfq", - .elevator_owner = THIS_MODULE, + .elevator_ktype = &cfq_ktype, + .elevator_name = "cfq", + .elevator_owner = THIS_MODULE, }; int cfq_init(void) @@ -905,9 +1905,12 @@ int cfq_init(void) return -ENOMEM; ret = elv_register(&iosched_cfq); - if (ret) - cfq_slab_kill(); + if (!ret) { + __module_get(THIS_MODULE); + return 0; + } + cfq_slab_kill(); return ret; } diff --git a/drivers/block/elevator.c b/drivers/block/elevator.c index 92cc7a9a5c63..1b4f6a70c0ca 100644 --- a/drivers/block/elevator.c +++ b/drivers/block/elevator.c @@ -437,7 +437,7 @@ int elv_may_queue(request_queue_t *q, int rw) if (e->ops->elevator_may_queue_fn) return e->ops->elevator_may_queue_fn(q, rw); - return 0; + return ELV_MQUEUE_MAY; } void elv_completed_request(request_queue_t *q, struct request *rq) diff --git a/drivers/block/ll_rw_blk.c b/drivers/block/ll_rw_blk.c index b3780ca0fdc0..3ba6430899df 100644 --- a/drivers/block/ll_rw_blk.c +++ b/drivers/block/ll_rw_blk.c @@ -243,6 +243,7 @@ void blk_queue_make_request(request_queue_t * q, make_request_fn * mfn) blk_queue_hardsect_size(q, 512); blk_queue_dma_alignment(q, 511); blk_queue_congestion_threshold(q); + q->nr_batching = BLK_BATCH_REQ; q->unplug_thresh = 4; /* hmm */ q->unplug_delay = (3 * HZ) / 1000; /* 3 milliseconds */ @@ -1511,8 +1512,10 @@ request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock) /* * all done */ - if (!elevator_init(q, NULL)) + if (!elevator_init(q, NULL)) { + blk_queue_congestion_threshold(q); return q; + } blk_cleanup_queue(q); out_init: @@ -1540,13 +1543,20 @@ static inline void blk_free_request(request_queue_t *q, struct request *rq) mempool_free(rq, q->rq.rq_pool); } -static inline struct request *blk_alloc_request(request_queue_t *q,int gfp_mask) +static inline struct request *blk_alloc_request(request_queue_t *q, int rw, + int gfp_mask) { struct request *rq = mempool_alloc(q->rq.rq_pool, gfp_mask); if (!rq) return NULL; + /* + * first three bits are identical in rq->flags and bio->bi_rw, + * see bio.h and blkdev.h + */ + rq->flags = rw; + if (!elv_set_request(q, rq, gfp_mask)) return rq; @@ -1558,7 +1568,7 @@ static inline struct request *blk_alloc_request(request_queue_t *q,int gfp_mask) * ioc_batching returns true if the ioc is a valid batching request and * should be given priority access to a request. */ -static inline int ioc_batching(struct io_context *ioc) +static inline int ioc_batching(request_queue_t *q, struct io_context *ioc) { if (!ioc) return 0; @@ -1568,7 +1578,7 @@ static inline int ioc_batching(struct io_context *ioc) * even if the batch times out, otherwise we could theoretically * lose wakeups. */ - return ioc->nr_batch_requests == BLK_BATCH_REQ || + return ioc->nr_batch_requests == q->nr_batching || (ioc->nr_batch_requests > 0 && time_before(jiffies, ioc->last_waited + BLK_BATCH_TIME)); } @@ -1579,12 +1589,12 @@ static inline int ioc_batching(struct io_context *ioc) * is the behaviour we want though - once it gets a wakeup it should be given * a nice run. */ -void ioc_set_batching(struct io_context *ioc) +void ioc_set_batching(request_queue_t *q, struct io_context *ioc) { - if (!ioc || ioc_batching(ioc)) + if (!ioc || ioc_batching(q, ioc)) return; - ioc->nr_batch_requests = BLK_BATCH_REQ; + ioc->nr_batch_requests = q->nr_batching; ioc->last_waited = jiffies; } @@ -1600,10 +1610,10 @@ static void freed_request(request_queue_t *q, int rw) if (rl->count[rw] < queue_congestion_off_threshold(q)) clear_queue_congested(q, rw); if (rl->count[rw]+1 <= q->nr_requests) { + smp_mb(); if (waitqueue_active(&rl->wait[rw])) wake_up(&rl->wait[rw]); - if (!waitqueue_active(&rl->wait[rw])) - blk_clear_queue_full(q, rw); + blk_clear_queue_full(q, rw); } if (unlikely(waitqueue_active(&rl->drain)) && !rl->count[READ] && !rl->count[WRITE]) @@ -1632,13 +1642,22 @@ static struct request *get_request(request_queue_t *q, int rw, int gfp_mask) * will be blocked. */ if (!blk_queue_full(q, rw)) { - ioc_set_batching(ioc); + ioc_set_batching(q, ioc); blk_set_queue_full(q, rw); } } - if (blk_queue_full(q, rw) - && !ioc_batching(ioc) && !elv_may_queue(q, rw)) { + switch (elv_may_queue(q, rw)) { + case ELV_MQUEUE_NO: + spin_unlock_irq(q->queue_lock); + goto out; + case ELV_MQUEUE_MAY: + break; + case ELV_MQUEUE_MUST: + goto get_rq; + } + + if (blk_queue_full(q, rw) && !ioc_batching(q, ioc)) { /* * The queue is full and the allocating process is not a * "batcher", and not exempted by the IO scheduler @@ -1647,12 +1666,13 @@ static struct request *get_request(request_queue_t *q, int rw, int gfp_mask) goto out; } +get_rq: rl->count[rw]++; if (rl->count[rw] >= queue_congestion_on_threshold(q)) set_queue_congested(q, rw); spin_unlock_irq(q->queue_lock); - rq = blk_alloc_request(q, gfp_mask); + rq = blk_alloc_request(q, rw, gfp_mask); if (!rq) { /* * Allocation failed presumably due to memory. Undo anything @@ -1667,17 +1687,11 @@ static struct request *get_request(request_queue_t *q, int rw, int gfp_mask) goto out; } - if (ioc_batching(ioc)) + if (ioc_batching(q, ioc)) ioc->nr_batch_requests--; INIT_LIST_HEAD(&rq->queuelist); - /* - * first three bits are identical in rq->flags and bio->bi_rw, - * see bio.h and blkdev.h - */ - rq->flags = rw; - rq->errors = 0; rq->rq_status = RQ_ACTIVE; rq->bio = rq->biotail = NULL; @@ -1726,7 +1740,7 @@ static struct request *get_request_wait(request_queue_t *q, int rw) * See ioc_batching, ioc_set_batching */ ioc = get_io_context(GFP_NOIO); - ioc_set_batching(ioc); + ioc_set_batching(q, ioc); put_io_context(ioc); } finish_wait(&rl->wait[rw], &wait); @@ -3082,6 +3096,9 @@ void put_io_context(struct io_context *ioc) if (atomic_dec_and_test(&ioc->refcount)) { if (ioc->aic && ioc->aic->dtor) ioc->aic->dtor(ioc->aic); + if (ioc->cic && ioc->cic->dtor) + ioc->cic->dtor(ioc->cic); + kmem_cache_free(iocontext_cachep, ioc); } } @@ -3095,14 +3112,15 @@ void exit_io_context(void) local_irq_save(flags); ioc = current->io_context; - if (ioc) { - if (ioc->aic && ioc->aic->exit) - ioc->aic->exit(ioc->aic); - put_io_context(ioc); - current->io_context = NULL; - } else - WARN_ON(1); + current->io_context = NULL; local_irq_restore(flags); + + if (ioc->aic && ioc->aic->exit) + ioc->aic->exit(ioc->aic); + if (ioc->cic && ioc->cic->exit) + ioc->cic->exit(ioc->cic); + + put_io_context(ioc); } /* @@ -3121,20 +3139,39 @@ struct io_context *get_io_context(int gfp_flags) local_irq_save(flags); ret = tsk->io_context; - if (ret == NULL) { - ret = kmem_cache_alloc(iocontext_cachep, GFP_ATOMIC); - if (ret) { - atomic_set(&ret->refcount, 1); - ret->pid = tsk->pid; - ret->last_waited = jiffies; /* doesn't matter... */ - ret->nr_batch_requests = 0; /* because this is 0 */ - ret->aic = NULL; + if (ret) + goto out; + + local_irq_restore(flags); + + ret = kmem_cache_alloc(iocontext_cachep, gfp_flags); + if (ret) { + atomic_set(&ret->refcount, 1); + ret->pid = tsk->pid; + ret->last_waited = jiffies; /* doesn't matter... */ + ret->nr_batch_requests = 0; /* because this is 0 */ + ret->aic = NULL; + ret->cic = NULL; + spin_lock_init(&ret->lock); + + local_irq_save(flags); + + /* + * very unlikely, someone raced with us in setting up the task + * io context. free new context and just grab a reference. + */ + if (!tsk->io_context) tsk->io_context = ret; + else { + kmem_cache_free(iocontext_cachep, ret); + ret = tsk->io_context; } - } - if (ret) + +out: atomic_inc(&ret->refcount); - local_irq_restore(flags); + local_irq_restore(flags); + } + return ret; } EXPORT_SYMBOL(get_io_context); diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h index 5e4a6ab84ecb..b2059869cb92 100644 --- a/include/linux/blkdev.h +++ b/include/linux/blkdev.h @@ -52,6 +52,20 @@ struct as_io_context { sector_t seek_mean; }; +struct cfq_queue; +struct cfq_io_context { + void (*dtor)(struct cfq_io_context *); + void (*exit)(struct cfq_io_context *); + + struct io_context *ioc; + + /* + * circular list of cfq_io_contexts belonging to a process io context + */ + struct list_head list; + struct cfq_queue *cfqq; +}; + /* * This is the per-process I/O subsystem state. It is refcounted and * kmalloc'ed. Currently all fields are modified in process io context @@ -67,7 +81,10 @@ struct io_context { unsigned long last_waited; /* Time last woken after wait for request */ int nr_batch_requests; /* Number of requests left in the batch */ + spinlock_t lock; + struct as_io_context *aic; + struct cfq_io_context *cic; }; void put_io_context(struct io_context *ioc); @@ -343,6 +360,7 @@ struct request_queue unsigned long nr_requests; /* Max # of requests */ unsigned int nr_congestion_on; unsigned int nr_congestion_off; + unsigned int nr_batching; unsigned short max_sectors; unsigned short max_hw_sectors; diff --git a/include/linux/elevator.h b/include/linux/elevator.h index 95cdfb5bb790..8cf0e3f290bf 100644 --- a/include/linux/elevator.h +++ b/include/linux/elevator.h @@ -130,4 +130,13 @@ extern int elv_try_last_merge(request_queue_t *, struct bio *); #define ELEVATOR_INSERT_BACK 2 #define ELEVATOR_INSERT_SORT 3 +/* + * return values from elevator_may_queue_fn + */ +enum { + ELV_MQUEUE_MAY, + ELV_MQUEUE_NO, + ELV_MQUEUE_MUST, +}; + #endif -- cgit v1.2.3 From d55249d351bc96f49e30bc3e5dfa1dad5034cc28 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Mon, 18 Oct 2004 18:01:53 -0700 Subject: [PATCH] convert jiffies <-> msecs for io schedulers The various io schedulers don't convert to and from jiffies and ms in their sysfs exported values. This patch adds that. Signed-off-by: Jens Axboe Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/block/as-iosched.c | 7 ++--- drivers/block/cfq-iosched.c | 58 +++++++++++++++++++++++----------------- drivers/block/deadline-iosched.c | 46 ++++++++++++++++++------------- 3 files changed, 64 insertions(+), 47 deletions(-) diff --git a/drivers/block/as-iosched.c b/drivers/block/as-iosched.c index b049848c19c3..0aa3ee8c309b 100644 --- a/drivers/block/as-iosched.c +++ b/drivers/block/as-iosched.c @@ -1962,10 +1962,10 @@ static ssize_t as_est_show(struct as_data *ad, char *page) return pos; } -#define SHOW_FUNCTION(__FUNC, __VAR) \ +#define SHOW_FUNCTION(__FUNC, __VAR) \ static ssize_t __FUNC(struct as_data *ad, char *page) \ -{ \ - return as_var_show(__VAR, (page)); \ +{ \ + return as_var_show(jiffies_to_msecs((__VAR)), (page)); \ } SHOW_FUNCTION(as_readexpire_show, ad->fifo_expire[REQ_SYNC]); SHOW_FUNCTION(as_writeexpire_show, ad->fifo_expire[REQ_ASYNC]); @@ -1982,6 +1982,7 @@ static ssize_t __FUNC(struct as_data *ad, const char *page, size_t count) \ *(__PTR) = (MIN); \ else if (*(__PTR) > (MAX)) \ *(__PTR) = (MAX); \ + *(__PTR) = msecs_to_jiffies(*(__PTR)); \ return ret; \ } STORE_FUNCTION(as_readexpire_store, &ad->fifo_expire[REQ_SYNC], 0, INT_MAX); diff --git a/drivers/block/cfq-iosched.c b/drivers/block/cfq-iosched.c index 738ff90bb2c1..cf7fc7609e67 100644 --- a/drivers/block/cfq-iosched.c +++ b/drivers/block/cfq-iosched.c @@ -1732,39 +1732,47 @@ cfq_status_show(struct cfq_data *cfqd, char *page) return len; } -#define SHOW_FUNCTION(__FUNC, __VAR) \ +#define SHOW_FUNCTION(__FUNC, __VAR, __CONV) \ static ssize_t __FUNC(struct cfq_data *cfqd, char *page) \ { \ - return cfq_var_show(__VAR, (page)); \ -} -SHOW_FUNCTION(cfq_quantum_show, cfqd->cfq_quantum); -SHOW_FUNCTION(cfq_queued_show, cfqd->cfq_queued); -SHOW_FUNCTION(cfq_fifo_expire_r_show, cfqd->cfq_fifo_expire_r); -SHOW_FUNCTION(cfq_fifo_expire_w_show, cfqd->cfq_fifo_expire_w); -SHOW_FUNCTION(cfq_fifo_batch_expire_show, cfqd->cfq_fifo_batch_expire); -SHOW_FUNCTION(cfq_find_best_show, cfqd->find_best_crq); -SHOW_FUNCTION(cfq_back_max_show, cfqd->cfq_back_max); -SHOW_FUNCTION(cfq_back_penalty_show, cfqd->cfq_back_penalty); + unsigned int __data = __VAR; \ + if (__CONV) \ + __data = jiffies_to_msecs(__data); \ + return cfq_var_show(__data, (page)); \ +} +SHOW_FUNCTION(cfq_quantum_show, cfqd->cfq_quantum, 0); +SHOW_FUNCTION(cfq_queued_show, cfqd->cfq_queued, 0); +SHOW_FUNCTION(cfq_fifo_expire_r_show, cfqd->cfq_fifo_expire_r, 1); +SHOW_FUNCTION(cfq_fifo_expire_w_show, cfqd->cfq_fifo_expire_w, 1); +SHOW_FUNCTION(cfq_fifo_batch_expire_show, cfqd->cfq_fifo_batch_expire, 1); +SHOW_FUNCTION(cfq_find_best_show, cfqd->find_best_crq, 0); +SHOW_FUNCTION(cfq_back_max_show, cfqd->cfq_back_max, 0); +SHOW_FUNCTION(cfq_back_penalty_show, cfqd->cfq_back_penalty, 0); #undef SHOW_FUNCTION -#define STORE_FUNCTION(__FUNC, __PTR, MIN, MAX) \ +#define STORE_FUNCTION(__FUNC, __PTR, MIN, MAX, __CONV) \ static ssize_t __FUNC(struct cfq_data *cfqd, const char *page, size_t count) \ { \ - int ret = cfq_var_store(__PTR, (page), count); \ - if (*(__PTR) < (MIN)) \ - *(__PTR) = (MIN); \ - else if (*(__PTR) > (MAX)) \ - *(__PTR) = (MAX); \ + unsigned int __data; \ + int ret = cfq_var_store(&__data, (page), count); \ + if (__data < (MIN)) \ + __data = (MIN); \ + else if (__data > (MAX)) \ + __data = (MAX); \ + if (__CONV) \ + *(__PTR) = msecs_to_jiffies(__data); \ + else \ + *(__PTR) = __data; \ return ret; \ } -STORE_FUNCTION(cfq_quantum_store, &cfqd->cfq_quantum, 1, UINT_MAX); -STORE_FUNCTION(cfq_queued_store, &cfqd->cfq_queued, 1, UINT_MAX); -STORE_FUNCTION(cfq_fifo_expire_r_store, &cfqd->cfq_fifo_expire_r, 1, UINT_MAX); -STORE_FUNCTION(cfq_fifo_expire_w_store, &cfqd->cfq_fifo_expire_w, 1, UINT_MAX); -STORE_FUNCTION(cfq_fifo_batch_expire_store, &cfqd->cfq_fifo_batch_expire, 0, UINT_MAX); -STORE_FUNCTION(cfq_find_best_store, &cfqd->find_best_crq, 0, 1); -STORE_FUNCTION(cfq_back_max_store, &cfqd->cfq_back_max, 0, UINT_MAX); -STORE_FUNCTION(cfq_back_penalty_store, &cfqd->cfq_back_penalty, 1, UINT_MAX); +STORE_FUNCTION(cfq_quantum_store, &cfqd->cfq_quantum, 1, UINT_MAX, 0); +STORE_FUNCTION(cfq_queued_store, &cfqd->cfq_queued, 1, UINT_MAX, 0); +STORE_FUNCTION(cfq_fifo_expire_r_store, &cfqd->cfq_fifo_expire_r, 1, UINT_MAX, 1); +STORE_FUNCTION(cfq_fifo_expire_w_store, &cfqd->cfq_fifo_expire_w, 1, UINT_MAX, 1); +STORE_FUNCTION(cfq_fifo_batch_expire_store, &cfqd->cfq_fifo_batch_expire, 0, UINT_MAX, 1); +STORE_FUNCTION(cfq_find_best_store, &cfqd->find_best_crq, 0, 1, 0); +STORE_FUNCTION(cfq_back_max_store, &cfqd->cfq_back_max, 0, UINT_MAX, 0); +STORE_FUNCTION(cfq_back_penalty_store, &cfqd->cfq_back_penalty, 1, UINT_MAX, 0); #undef STORE_FUNCTION static struct cfq_fs_entry cfq_quantum_entry = { diff --git a/drivers/block/deadline-iosched.c b/drivers/block/deadline-iosched.c index 0d3e2411f1d3..f482e8bdb4d6 100644 --- a/drivers/block/deadline-iosched.c +++ b/drivers/block/deadline-iosched.c @@ -805,33 +805,41 @@ deadline_var_store(unsigned int *var, const char *page, size_t count) return count; } -#define SHOW_FUNCTION(__FUNC, __VAR) \ +#define SHOW_FUNCTION(__FUNC, __VAR, __CONV) \ static ssize_t __FUNC(struct deadline_data *dd, char *page) \ { \ - return deadline_var_show(__VAR, (page)); \ -} -SHOW_FUNCTION(deadline_readexpire_show, dd->fifo_expire[READ]); -SHOW_FUNCTION(deadline_writeexpire_show, dd->fifo_expire[WRITE]); -SHOW_FUNCTION(deadline_writesstarved_show, dd->writes_starved); -SHOW_FUNCTION(deadline_frontmerges_show, dd->front_merges); -SHOW_FUNCTION(deadline_fifobatch_show, dd->fifo_batch); + unsigned int __data = __VAR; \ + if (__CONV) \ + __data = jiffies_to_msecs(__data); \ + return deadline_var_show(__data, (page)); \ +} +SHOW_FUNCTION(deadline_readexpire_show, dd->fifo_expire[READ], 1); +SHOW_FUNCTION(deadline_writeexpire_show, dd->fifo_expire[WRITE], 1); +SHOW_FUNCTION(deadline_writesstarved_show, dd->writes_starved, 0); +SHOW_FUNCTION(deadline_frontmerges_show, dd->front_merges, 0); +SHOW_FUNCTION(deadline_fifobatch_show, dd->fifo_batch, 0); #undef SHOW_FUNCTION -#define STORE_FUNCTION(__FUNC, __PTR, MIN, MAX) \ +#define STORE_FUNCTION(__FUNC, __PTR, MIN, MAX, __CONV) \ static ssize_t __FUNC(struct deadline_data *dd, const char *page, size_t count) \ { \ - int ret = deadline_var_store(__PTR, (page), count); \ - if (*(__PTR) < (MIN)) \ - *(__PTR) = (MIN); \ - else if (*(__PTR) > (MAX)) \ - *(__PTR) = (MAX); \ + unsigned int __data; \ + int ret = deadline_var_store(&__data, (page), count); \ + if (__data < (MIN)) \ + __data = (MIN); \ + else if (__data > (MAX)) \ + __data = (MAX); \ + if (__CONV) \ + *(__PTR) = msecs_to_jiffies(__data); \ + else \ + *(__PTR) = __data; \ return ret; \ } -STORE_FUNCTION(deadline_readexpire_store, &dd->fifo_expire[READ], 0, INT_MAX); -STORE_FUNCTION(deadline_writeexpire_store, &dd->fifo_expire[WRITE], 0, INT_MAX); -STORE_FUNCTION(deadline_writesstarved_store, &dd->writes_starved, INT_MIN, INT_MAX); -STORE_FUNCTION(deadline_frontmerges_store, &dd->front_merges, 0, 1); -STORE_FUNCTION(deadline_fifobatch_store, &dd->fifo_batch, 0, INT_MAX); +STORE_FUNCTION(deadline_readexpire_store, &dd->fifo_expire[READ], 0, INT_MAX, 1); +STORE_FUNCTION(deadline_writeexpire_store, &dd->fifo_expire[WRITE], 0, INT_MAX, 1); +STORE_FUNCTION(deadline_writesstarved_store, &dd->writes_starved, INT_MIN, INT_MAX, 0); +STORE_FUNCTION(deadline_frontmerges_store, &dd->front_merges, 0, 1, 0); +STORE_FUNCTION(deadline_fifobatch_store, &dd->fifo_batch, 0, INT_MAX, 0); #undef STORE_FUNCTION static struct deadline_fs_entry deadline_readexpire_entry = { -- cgit v1.2.3 From 1cd05eadec387f138424603a1b34761506443707 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 18 Oct 2004 18:02:05 -0700 Subject: [PATCH] don't export blkdev_open and def_blk_ops Already since 2.4 all block devices use block_device_operations and shouldn't deal with file operations directly. Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/block_dev.c | 6 +----- include/linux/fs.h | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/fs/block_dev.c b/fs/block_dev.c index 5c3f09b86172..28aac88114de 100644 --- a/fs/block_dev.c +++ b/fs/block_dev.c @@ -666,7 +666,7 @@ int blkdev_get(struct block_device *bdev, mode_t mode, unsigned flags) EXPORT_SYMBOL(blkdev_get); -int blkdev_open(struct inode * inode, struct file * filp) +static int blkdev_open(struct inode * inode, struct file * filp) { struct block_device *bdev; int res; @@ -695,8 +695,6 @@ int blkdev_open(struct inode * inode, struct file * filp) return res; } -EXPORT_SYMBOL(blkdev_open); - int blkdev_put(struct block_device *bdev) { int ret = 0; @@ -798,8 +796,6 @@ struct file_operations def_blk_fops = { .sendfile = generic_file_sendfile, }; -EXPORT_SYMBOL(def_blk_fops); - int ioctl_by_bdev(struct block_device *bdev, unsigned cmd, unsigned long arg) { int res; diff --git a/include/linux/fs.h b/include/linux/fs.h index 2a13baa4250b..dc457c3fede2 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1255,7 +1255,6 @@ extern struct block_device *bdget(dev_t); extern void bd_set_size(struct block_device *, loff_t size); extern void bd_forget(struct inode *inode); extern void bdput(struct block_device *); -extern int blkdev_open(struct inode *, struct file *); extern struct block_device *open_by_devnum(dev_t, unsigned); extern struct file_operations def_blk_fops; extern struct address_space_operations def_blk_aops; -- cgit v1.2.3 From 8704c669a5587e4b06807a6394e453582eb0633e Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 18 Oct 2004 18:02:18 -0700 Subject: [PATCH] remove dead code from fs/mbcache.c mb_cache_entry_takeout and mb_cache_entry_dup are totally unused. Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/mbcache.c | 33 --------------------------------- include/linux/mbcache.h | 2 -- 2 files changed, 35 deletions(-) diff --git a/fs/mbcache.c b/fs/mbcache.c index dbc4443e6949..988161cb0a77 100644 --- a/fs/mbcache.c +++ b/fs/mbcache.c @@ -65,9 +65,7 @@ EXPORT_SYMBOL(mb_cache_destroy); EXPORT_SYMBOL(mb_cache_entry_alloc); EXPORT_SYMBOL(mb_cache_entry_insert); EXPORT_SYMBOL(mb_cache_entry_release); -EXPORT_SYMBOL(mb_cache_entry_takeout); EXPORT_SYMBOL(mb_cache_entry_free); -EXPORT_SYMBOL(mb_cache_entry_dup); EXPORT_SYMBOL(mb_cache_entry_get); #if !defined(MB_CACHE_INDEXES_COUNT) || (MB_CACHE_INDEXES_COUNT > 0) EXPORT_SYMBOL(mb_cache_entry_find_first); @@ -455,23 +453,6 @@ mb_cache_entry_release(struct mb_cache_entry *ce) } -/* - * mb_cache_entry_takeout() - * - * Take a cache entry out of the cache, making it invalid. The entry can later - * be re-inserted using mb_cache_entry_insert(), or released using - * mb_cache_entry_release(). - */ -void -mb_cache_entry_takeout(struct mb_cache_entry *ce) -{ - spin_lock(&mb_cache_spinlock); - mb_assert(list_empty(&ce->e_lru_list)); - __mb_cache_entry_unhash(ce); - spin_unlock(&mb_cache_spinlock); -} - - /* * mb_cache_entry_free() * @@ -488,20 +469,6 @@ mb_cache_entry_free(struct mb_cache_entry *ce) } -/* - * mb_cache_entry_dup() - * - * Duplicate a handle to a cache entry (does not duplicate the cache entry - * itself). After the call, both the old and the new handle must be released. - */ -struct mb_cache_entry * -mb_cache_entry_dup(struct mb_cache_entry *ce) -{ - atomic_inc(&ce->e_used); - return ce; -} - - /* * mb_cache_entry_get() * diff --git a/include/linux/mbcache.h b/include/linux/mbcache.h index 7738749e1285..15a806ad61ee 100644 --- a/include/linux/mbcache.h +++ b/include/linux/mbcache.h @@ -56,9 +56,7 @@ int mb_cache_entry_insert(struct mb_cache_entry *, struct block_device *, sector_t, unsigned int[]); void mb_cache_entry_rehash(struct mb_cache_entry *, unsigned int[]); void mb_cache_entry_release(struct mb_cache_entry *); -void mb_cache_entry_takeout(struct mb_cache_entry *); void mb_cache_entry_free(struct mb_cache_entry *); -struct mb_cache_entry *mb_cache_entry_dup(struct mb_cache_entry *); struct mb_cache_entry *mb_cache_entry_get(struct mb_cache *, struct block_device *, sector_t); -- cgit v1.2.3 From abbb0399dd6746829c6dd9934a2cbb9e1d091b0b Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 18 Oct 2004 18:02:30 -0700 Subject: [PATCH] remove posix_acl_masq_nfs_mode Completely unused but exported function in fs/posix_acl.c Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/posix_acl.c | 42 ------------------------------------------ include/linux/posix_acl.h | 1 - 2 files changed, 43 deletions(-) diff --git a/fs/posix_acl.c b/fs/posix_acl.c index c802d5a2f16a..97fbb86195ef 100644 --- a/fs/posix_acl.c +++ b/fs/posix_acl.c @@ -29,7 +29,6 @@ EXPORT_SYMBOL(posix_acl_equiv_mode); EXPORT_SYMBOL(posix_acl_from_mode); EXPORT_SYMBOL(posix_acl_create_masq); EXPORT_SYMBOL(posix_acl_chmod_masq); -EXPORT_SYMBOL(posix_acl_masq_nfs_mode); EXPORT_SYMBOL(posix_acl_permission); /* @@ -380,44 +379,3 @@ posix_acl_chmod_masq(struct posix_acl *acl, mode_t mode) return 0; } - -/* - * Adjust the mode parameter so that NFSv2 grants nobody permissions - * that may not be granted by the ACL. This is necessary because NFSv2 - * may compute access permissions on the client side, and may serve cached - * data whenever it assumes access would be granted. Since ACLs may also - * be used to deny access to specific users, the minimal permissions - * for secure operation over NFSv2 are very restrictive. Permissions - * granted to users via Access Control Lists will not be effective over - * NFSv2. - * - * Privilege escalation can only happen for read operations, as writes are - * always carried out on the NFS server, where the proper access checks are - * implemented. - */ -int -posix_acl_masq_nfs_mode(struct posix_acl *acl, mode_t *mode_p) -{ - struct posix_acl_entry *pa, *pe; int min_perm = S_IRWXO; - - FOREACH_ACL_ENTRY(pa, acl, pe) { - switch(pa->e_tag) { - case ACL_USER_OBJ: - break; - - case ACL_USER: - case ACL_GROUP_OBJ: - case ACL_GROUP: - case ACL_MASK: - case ACL_OTHER: - min_perm &= pa->e_perm; - break; - - default: - return -EIO; - } - } - *mode_p = (*mode_p & ~(S_IRWXG|S_IRWXO)) | (min_perm << 3) | min_perm; - - return 0; -} diff --git a/include/linux/posix_acl.h b/include/linux/posix_acl.h index aff9a6adb39e..fc74ef3fef36 100644 --- a/include/linux/posix_acl.h +++ b/include/linux/posix_acl.h @@ -79,7 +79,6 @@ extern struct posix_acl *posix_acl_from_mode(mode_t, int); extern int posix_acl_equiv_mode(const struct posix_acl *, mode_t *); extern int posix_acl_create_masq(struct posix_acl *, mode_t *); extern int posix_acl_chmod_masq(struct posix_acl *, mode_t); -extern int posix_acl_masq_nfs_mode(struct posix_acl *, mode_t *); extern struct posix_acl *get_posix_acl(struct inode *, int); extern int set_posix_acl(struct inode *, int, struct posix_acl *); -- cgit v1.2.3 From 733902e4992a01c20e5184b77b108373ae0c80ee Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 18 Oct 2004 18:02:40 -0700 Subject: [PATCH] don't export shmem_file_setup Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- mm/shmem.c | 2 -- mm/tiny-shmem.c | 2 -- 2 files changed, 4 deletions(-) diff --git a/mm/shmem.c b/mm/shmem.c index 296a61073ef6..2ba02bf2672d 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -2164,5 +2164,3 @@ int shmem_zero_setup(struct vm_area_struct *vma) vma->vm_ops = &shmem_vm_ops; return 0; } - -EXPORT_SYMBOL(shmem_file_setup); diff --git a/mm/tiny-shmem.c b/mm/tiny-shmem.c index 90abc63db367..c13a2161bca2 100644 --- a/mm/tiny-shmem.c +++ b/mm/tiny-shmem.c @@ -120,5 +120,3 @@ int shmem_unuse(swp_entry_t entry, struct page *page) { return 0; } - -EXPORT_SYMBOL(shmem_file_setup); -- cgit v1.2.3 From 71ccd42c4e97294bb1eefb577f943b97b400f4a8 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 18 Oct 2004 18:02:52 -0700 Subject: [PATCH] remove pm_find, unexport pm_send cutting back some unused legacy PM code Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- include/linux/pm.h | 5 ----- kernel/power/pm.c | 31 ------------------------------- 2 files changed, 36 deletions(-) diff --git a/include/linux/pm.h b/include/linux/pm.h index 7bfd2d43963e..6446e4f65e93 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -143,11 +143,6 @@ int pm_send(struct pm_dev *dev, pm_request_t rqst, void *data); */ int pm_send_all(pm_request_t rqst, void *data); -/* - * Find a device - */ -struct pm_dev *pm_find(pm_dev_t type, struct pm_dev *from); - static inline void pm_access(struct pm_dev *dev) {} static inline void pm_dev_idle(struct pm_dev *dev) {} diff --git a/kernel/power/pm.c b/kernel/power/pm.c index d1bc943072d4..8fca5822a807 100644 --- a/kernel/power/pm.c +++ b/kernel/power/pm.c @@ -256,41 +256,10 @@ int pm_send_all(pm_request_t rqst, void *data) return 0; } -/** - * pm_find - find a device - * @type: type of device - * @from: where to start looking - * - * Scan the power management list for devices of a specific type. The - * return value for a matching device may be passed to further calls - * to this function to find further matches. A %NULL indicates the end - * of the list. - * - * To search from the beginning pass %NULL as the @from value. - * - * The caller MUST hold the pm_devs_lock lock when calling this - * function. The instant that the lock is dropped all pointers returned - * may become invalid. - */ - -struct pm_dev *pm_find(pm_dev_t type, struct pm_dev *from) -{ - struct list_head *entry = from ? from->entry.next:pm_devs.next; - while (entry != &pm_devs) { - struct pm_dev *dev = list_entry(entry, struct pm_dev, entry); - if (type == PM_UNKNOWN_DEV || dev->type == type) - return dev; - entry = entry->next; - } - return NULL; -} - EXPORT_SYMBOL(pm_register); EXPORT_SYMBOL(pm_unregister); EXPORT_SYMBOL(pm_unregister_all); -EXPORT_SYMBOL(pm_send); EXPORT_SYMBOL(pm_send_all); -EXPORT_SYMBOL(pm_find); EXPORT_SYMBOL(pm_active); -- cgit v1.2.3 From c78c27a2aba951456b6facc549ac11df8b709232 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 18 Oct 2004 18:03:02 -0700 Subject: [PATCH] remove dead code and exports from signal.c Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- include/linux/sched.h | 1 - kernel/signal.c | 47 ----------------------------------------------- 2 files changed, 48 deletions(-) diff --git a/include/linux/sched.h b/include/linux/sched.h index bbcd869e9304..e369a601ae0b 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -838,7 +838,6 @@ extern int force_sigsegv(int, struct task_struct *); extern int force_sig_info(int, struct siginfo *, struct task_struct *); extern int __kill_pg_info(int sig, struct siginfo *info, pid_t pgrp); extern int kill_pg_info(int, struct siginfo *, pid_t); -extern int kill_sl_info(int, struct siginfo *, pid_t); extern int kill_proc_info(int, struct siginfo *, pid_t); extern void do_notify_parent(struct task_struct *, int); extern void force_sig(int, struct task_struct *); diff --git a/kernel/signal.c b/kernel/signal.c index f67390806d73..ba039fab37e8 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -1143,36 +1143,6 @@ kill_pg_info(int sig, struct siginfo *info, pid_t pgrp) return retval; } -/* - * kill_sl_info() sends a signal to the session leader: this is used - * to send SIGHUP to the controlling process of a terminal when - * the connection is lost. - */ - - -int -kill_sl_info(int sig, struct siginfo *info, pid_t sid) -{ - int err, retval = -EINVAL; - struct task_struct *p; - - if (sid <= 0) - goto out; - - retval = -ESRCH; - read_lock(&tasklist_lock); - do_each_task_pid(sid, PIDTYPE_SID, p) { - if (!p->signal->leader) - continue; - err = group_send_sig_info(sig, info, p); - if (retval) - retval = err; - } while_each_task_pid(sid, PIDTYPE_SID, p); - read_unlock(&tasklist_lock); -out: - return retval; -} - int kill_proc_info(int sig, struct siginfo *info, pid_t pid) { @@ -1308,12 +1278,6 @@ kill_pg(pid_t pgrp, int sig, int priv) return kill_pg_info(sig, (void *)(long)(priv != 0), pgrp); } -int -kill_sl(pid_t sess, int sig, int priv) -{ - return kill_sl_info(sig, (void *)(long)(priv != 0), sess); -} - int kill_proc(pid_t pid, int sig, int priv) { @@ -1978,22 +1942,11 @@ relock: EXPORT_SYMBOL(recalc_sigpending); EXPORT_SYMBOL_GPL(dequeue_signal); EXPORT_SYMBOL(flush_signals); -EXPORT_SYMBOL(force_sig); -EXPORT_SYMBOL(force_sig_info); EXPORT_SYMBOL(kill_pg); -EXPORT_SYMBOL(kill_pg_info); EXPORT_SYMBOL(kill_proc); -EXPORT_SYMBOL(kill_proc_info); -EXPORT_SYMBOL(kill_sl); -EXPORT_SYMBOL(kill_sl_info); EXPORT_SYMBOL(ptrace_notify); EXPORT_SYMBOL(send_sig); EXPORT_SYMBOL(send_sig_info); -EXPORT_SYMBOL(send_group_sig_info); -EXPORT_SYMBOL(sigqueue_alloc); -EXPORT_SYMBOL(sigqueue_free); -EXPORT_SYMBOL(send_sigqueue); -EXPORT_SYMBOL(send_group_sigqueue); EXPORT_SYMBOL(sigprocmask); EXPORT_SYMBOL(block_all_signals); EXPORT_SYMBOL(unblock_all_signals); -- cgit v1.2.3 From 546f3d24fddc30f3e545d324d9a9b2bdb284803c Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 18 Oct 2004 18:03:14 -0700 Subject: [PATCH] unexport proc_sys_root Only used by kernel/sysctl.c which absolutely can't be modular Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/proc/root.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/fs/proc/root.c b/fs/proc/root.c index 6151f0592f28..76779d4e5a75 100644 --- a/fs/proc/root.c +++ b/fs/proc/root.c @@ -149,9 +149,6 @@ struct proc_dir_entry proc_root = { .parent = &proc_root, }; -#ifdef CONFIG_SYSCTL -EXPORT_SYMBOL(proc_sys_root); -#endif EXPORT_SYMBOL(proc_symlink); EXPORT_SYMBOL(proc_mkdir); EXPORT_SYMBOL(create_proc_entry); -- cgit v1.2.3 From b2963232d0b83f48a0530d08266257efdf83c9e7 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 18 Oct 2004 18:03:25 -0700 Subject: [PATCH] unexport is_subdir and shrink_dcache_anon Two dcache.c functions that shouldn't be used by filesystems directly (probably a leftover of the intermezzo mess). Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/dcache.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/fs/dcache.c b/fs/dcache.c index ec5668a4136e..3c17f9815b7e 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -1656,8 +1656,6 @@ EXPORT_SYMBOL(dget_locked); EXPORT_SYMBOL(dput); EXPORT_SYMBOL(find_inode_number); EXPORT_SYMBOL(have_submounts); -EXPORT_SYMBOL(is_subdir); EXPORT_SYMBOL(names_cachep); -EXPORT_SYMBOL(shrink_dcache_anon); EXPORT_SYMBOL(shrink_dcache_parent); EXPORT_SYMBOL(shrink_dcache_sb); -- cgit v1.2.3 From 239ff74bb7721294241a8d4862502b0f7e011c8a Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 18 Oct 2004 18:03:37 -0700 Subject: [PATCH] unexport devfs_mk_symlink Only legit user is the partitioning code, in addition some uml code is still using despite the uml people beeing told to fix it at least two times. Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/devfs/base.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/devfs/base.c b/fs/devfs/base.c index a62d9412e73e..65e299a2d97e 100644 --- a/fs/devfs/base.c +++ b/fs/devfs/base.c @@ -1802,7 +1802,6 @@ static int __init devfs_setup(char *str) __setup("devfs=", devfs_setup); -EXPORT_SYMBOL(devfs_mk_symlink); EXPORT_SYMBOL(devfs_mk_dir); EXPORT_SYMBOL(devfs_remove); -- cgit v1.2.3 From 0f20e117ee86f781cbebb343c93b43fc1dc5e068 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 18 Oct 2004 18:03:48 -0700 Subject: [PATCH] unexport do_execve/do_select These are basically shared code for native/32bit compat code, but as CONFIG_COMPAT is a bool there's no need to export them. Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/exec.c | 2 -- fs/select.c | 2 -- 2 files changed, 4 deletions(-) diff --git a/fs/exec.c b/fs/exec.c index 52ffe4e3295d..4124b39bdd27 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1187,8 +1187,6 @@ out_ret: return retval; } -EXPORT_SYMBOL(do_execve); - int set_binfmt(struct linux_binfmt *new) { struct linux_binfmt *old = current->binfmt; diff --git a/fs/select.c b/fs/select.c index e0a87cb4f733..57e776cafb3b 100644 --- a/fs/select.c +++ b/fs/select.c @@ -268,8 +268,6 @@ int do_select(int n, fd_set_bits *fds, long *timeout) return retval; } -EXPORT_SYMBOL(do_select); - static void *select_bits_alloc(int size) { return kmalloc(6 * size, GFP_KERNEL); -- cgit v1.2.3 From a58eab601a5c729fece4f5e57f0cb79088aeb1a4 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 18 Oct 2004 18:04:00 -0700 Subject: [PATCH] unexport exit_mm Not exactly a thing we want done from modules, and no module uses it anyway. Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- kernel/exit.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/kernel/exit.c b/kernel/exit.c index e242c22dee36..a8ae81ed1d41 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -512,8 +512,6 @@ void exit_mm(struct task_struct *tsk) __exit_mm(tsk); } -EXPORT_SYMBOL(exit_mm); - static inline void choose_new_parent(task_t *p, task_t *reaper, task_t *child_reaper) { /* -- cgit v1.2.3 From 11dd96e05bf838e02da7109cd12e16963aac5bf7 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 18 Oct 2004 18:04:12 -0700 Subject: [PATCH] unexport files_lock and put_filp Rather lowlevel functions that modules shouldn't mess with and fortunately currently don't. Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/file_table.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/fs/file_table.c b/fs/file_table.c index 3750a140ef43..e9ce5279ef6d 100644 --- a/fs/file_table.c +++ b/fs/file_table.c @@ -24,11 +24,9 @@ struct files_stat_struct files_stat = { EXPORT_SYMBOL(files_stat); /* Needed by unix.o */ -/* public *and* exported. Not pretty! */ +/* public. Not pretty! */ spinlock_t __cacheline_aligned_in_smp files_lock = SPIN_LOCK_UNLOCKED; -EXPORT_SYMBOL(files_lock); - static spinlock_t filp_count_lock = SPIN_LOCK_UNLOCKED; /* slab constructors and destructors are called from arbitrary @@ -199,8 +197,6 @@ void put_filp(struct file *file) } } -EXPORT_SYMBOL(put_filp); - void file_move(struct file *file, struct list_head *list) { if (!list) -- cgit v1.2.3 From d3259ff183acbf88020dab33028b9ef81353b47c Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 18 Oct 2004 18:04:24 -0700 Subject: [PATCH] unexport f_delown Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/fcntl.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/fs/fcntl.c b/fs/fcntl.c index ee380e7b9569..da78d7bba880 100644 --- a/fs/fcntl.c +++ b/fs/fcntl.c @@ -291,8 +291,6 @@ void f_delown(struct file *filp) f_modown(filp, 0, 0, 0, 1); } -EXPORT_SYMBOL(f_delown); - static long do_fcntl(int fd, unsigned int cmd, unsigned long arg, struct file *filp) { -- cgit v1.2.3 From e0f9efde54309902718b79e7ccb9c9fdc3126a9f Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 18 Oct 2004 18:04:36 -0700 Subject: [PATCH] unexport lookup_create Besides namei.c it's only used in the SN2 hwgraph code which can't be modular (and will be removed soon) Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/namei.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/namei.c b/fs/namei.c index b00bcaa91ee2..bf259a9c5b21 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2438,7 +2438,6 @@ EXPORT_SYMBOL(follow_up); EXPORT_SYMBOL(get_write_access); /* binfmt_aout */ EXPORT_SYMBOL(getname); EXPORT_SYMBOL(lock_rename); -EXPORT_SYMBOL(lookup_create); EXPORT_SYMBOL(lookup_hash); EXPORT_SYMBOL(lookup_one_len); EXPORT_SYMBOL(page_follow_link); -- cgit v1.2.3 From 7a814bf85d8ad31754e626f4a16da7155f7a21f6 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 18 Oct 2004 18:04:48 -0700 Subject: [PATCH] remove wake_up_all_sync no user in sight Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- include/linux/wait.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/linux/wait.h b/include/linux/wait.h index 146cceee0221..8b3a2b86d92a 100644 --- a/include/linux/wait.h +++ b/include/linux/wait.h @@ -150,7 +150,6 @@ wait_queue_head_t *FASTCALL(bit_waitqueue(void *, int)); #define wake_up(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1, NULL) #define wake_up_nr(x, nr) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, nr, NULL) #define wake_up_all(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 0, NULL) -#define wake_up_all_sync(x) __wake_up_sync((x),TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 0) #define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL) #define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL) #define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL) -- cgit v1.2.3 From 98291a7763a8b3db9135b9625de483e2821c6c96 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 18 Oct 2004 18:05:00 -0700 Subject: [PATCH] remove set_fs_root/set_fs_pwd Not exactly something we want modules to mess around with. Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/namespace.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/fs/namespace.c b/fs/namespace.c index 961b6ae00458..b09b08807f2c 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -1209,8 +1209,6 @@ void set_fs_root(struct fs_struct *fs, struct vfsmount *mnt, } } -EXPORT_SYMBOL(set_fs_root); - /* * Replace the fs->{pwdmnt,pwd} with {mnt,dentry}. Put the old values. * It can block. Requires the big lock held. @@ -1234,8 +1232,6 @@ void set_fs_pwd(struct fs_struct *fs, struct vfsmount *mnt, } } -EXPORT_SYMBOL(set_fs_pwd); - static void chroot_fs_refs(struct nameidata *old_nd, struct nameidata *new_nd) { struct task_struct *g, *p; -- cgit v1.2.3 From 42017c2e0ae391565af3c894020379194f668d54 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 18 Oct 2004 18:05:13 -0700 Subject: [PATCH] generic acl support for ->permission Currently we every filesystem with Posix ACLs has it's own reimplemtation of the generic permission checking code with additonal ACL support. This patch - adds an optional callback to vfs_permission that filesystems can use for ACL support (and renames it to generic_permission because the old name was wrong - it wasn't like the other vfs_* functions at all) - uses it in ext2, ext3 and jfs. XFS will follow a little later as it's permission checking is burried under several layers of abstraction. From: Dave Kleikamp jfs doesn't currently set MS_POSIXACL (it doesn't require the acl mount option), so this test would fail here. The patch below will set it. Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/cifs/CHANGES | 4 +-- fs/cifs/cifsfs.c | 2 +- fs/exec.c | 2 +- fs/ext2/acl.c | 66 +++++++++------------------------------- fs/ext3/acl.c | 66 +++++++++------------------------------- fs/hfs/inode.c | 2 +- fs/hfsplus/inode.c | 2 +- fs/hostfs/hostfs_kern.c | 2 +- fs/jfs/acl.c | 81 ++++++------------------------------------------- fs/jfs/super.c | 4 +++ fs/namei.c | 33 ++++++++++++++------ fs/nfs/dir.c | 2 +- fs/proc/base.c | 2 +- include/linux/fs.h | 4 ++- 14 files changed, 79 insertions(+), 193 deletions(-) diff --git a/fs/cifs/CHANGES b/fs/cifs/CHANGES index 087be36211eb..2a1924078059 100644 --- a/fs/cifs/CHANGES +++ b/fs/cifs/CHANGES @@ -7,8 +7,8 @@ or out of order NULL pointer checks in little used error paths). Version 1.21 ------------ -Add new mount parm to control whether mode check (vfs_permission) is done on -the client. If Unix extensions are enabled and the uids on the client +Add new mount parm to control whether mode check (generic_permission) is done +on the client. If Unix extensions are enabled and the uids on the client and server do not match, client permission checks are meaningless on server uids that do not exist on the client (this does not affect the normal ACL check which occurs on the server). Fix default uid diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c index 729cdba71530..996fc298a306 100644 --- a/fs/cifs/cifsfs.c +++ b/fs/cifs/cifsfs.c @@ -200,7 +200,7 @@ static int cifs_permission(struct inode * inode, int mask, struct nameidata *nd) on the client (above and beyond ACL on servers) for servers which do not support setting and viewing mode bits, so allowing client to check permissions is useful */ - return vfs_permission(inode, mask); + return generic_permission(inode, mask, NULL); } static kmem_cache_t *cifs_inode_cachep; diff --git a/fs/exec.c b/fs/exec.c index 4124b39bdd27..4915bffb045d 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -886,7 +886,7 @@ int prepare_binprm(struct linux_binprm *bprm) mode = inode->i_mode; /* * Check execute perms again - if the caller has CAP_DAC_OVERRIDE, - * vfs_permission lets a non-executable through + * generic_permission lets a non-executable through */ if (!(mode & 0111)) /* with at least _one_ execute bit set */ return -EACCES; diff --git a/fs/ext2/acl.c b/fs/ext2/acl.c index 89d1df91411f..0d961d5e00ed 100644 --- a/fs/ext2/acl.c +++ b/fs/ext2/acl.c @@ -280,60 +280,24 @@ ext2_set_acl(struct inode *inode, int type, struct posix_acl *acl) return error; } -/* - * Inode operation permission(). - * - * inode->i_sem: don't care - */ -int -ext2_permission(struct inode *inode, int mask, struct nameidata *nd) +static int +ext2_check_acl(struct inode *inode, int mask) { - int mode = inode->i_mode; - - /* Nobody gets write access to a read-only fs */ - if ((mask & MAY_WRITE) && IS_RDONLY(inode) && - (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) - return -EROFS; - /* Nobody gets write access to an immutable file */ - if ((mask & MAY_WRITE) && IS_IMMUTABLE(inode)) - return -EACCES; - if (current->fsuid == inode->i_uid) { - mode >>= 6; - } else if (test_opt(inode->i_sb, POSIX_ACL)) { - struct posix_acl *acl; - - /* The access ACL cannot grant access if the group class - permission bits don't contain all requested permissions. */ - if (((mode >> 3) & mask & S_IRWXO) != mask) - goto check_groups; - acl = ext2_get_acl(inode, ACL_TYPE_ACCESS); - if (acl) { - int error = posix_acl_permission(inode, acl, mask); - posix_acl_release(acl); - if (error == -EACCES) - goto check_capabilities; - return error; - } else - goto check_groups; - } else { -check_groups: - if (in_group_p(inode->i_gid)) - mode >>= 3; + struct posix_acl *acl = ext2_get_acl(inode, ACL_TYPE_ACCESS); + + if (acl) { + int error = posix_acl_permission(inode, acl, mask); + posix_acl_release(acl); + return error; } - if ((mode & mask & S_IRWXO) == mask) - return 0; -check_capabilities: - /* Allowed to override Discretionary Access Control? */ - if (!(mask & MAY_EXEC) || - (inode->i_mode & S_IXUGO) || S_ISDIR(inode->i_mode)) - if (capable(CAP_DAC_OVERRIDE)) - return 0; - /* Read and search granted if capable(CAP_DAC_READ_SEARCH) */ - if (capable(CAP_DAC_READ_SEARCH) && ((mask == MAY_READ) || - (S_ISDIR(inode->i_mode) && !(mask & MAY_WRITE)))) - return 0; - return -EACCES; + return -EAGAIN; +} + +int +ext2_permission(struct inode *inode, int mask, struct nameidata *nd) +{ + return generic_permission(inode, mask, ext2_check_acl); } /* diff --git a/fs/ext3/acl.c b/fs/ext3/acl.c index a3cf77de0e43..a335fd134352 100644 --- a/fs/ext3/acl.c +++ b/fs/ext3/acl.c @@ -285,60 +285,24 @@ ext3_set_acl(handle_t *handle, struct inode *inode, int type, return error; } -/* - * Inode operation permission(). - * - * inode->i_sem: don't care - */ -int -ext3_permission(struct inode *inode, int mask, struct nameidata *nd) +static int +ext3_check_acl(struct inode *inode, int mask) { - int mode = inode->i_mode; - - /* Nobody gets write access to a read-only fs */ - if ((mask & MAY_WRITE) && IS_RDONLY(inode) && - (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) - return -EROFS; - /* Nobody gets write access to an immutable file */ - if ((mask & MAY_WRITE) && IS_IMMUTABLE(inode)) - return -EACCES; - if (current->fsuid == inode->i_uid) { - mode >>= 6; - } else if (test_opt(inode->i_sb, POSIX_ACL)) { - struct posix_acl *acl; - - /* The access ACL cannot grant access if the group class - permission bits don't contain all requested permissions. */ - if (((mode >> 3) & mask & S_IRWXO) != mask) - goto check_groups; - acl = ext3_get_acl(inode, ACL_TYPE_ACCESS); - if (acl) { - int error = posix_acl_permission(inode, acl, mask); - posix_acl_release(acl); - if (error == -EACCES) - goto check_capabilities; - return error; - } else - goto check_groups; - } else { -check_groups: - if (in_group_p(inode->i_gid)) - mode >>= 3; + struct posix_acl *acl = ext3_get_acl(inode, ACL_TYPE_ACCESS); + + if (acl) { + int error = posix_acl_permission(inode, acl, mask); + posix_acl_release(acl); + return error; } - if ((mode & mask & S_IRWXO) == mask) - return 0; -check_capabilities: - /* Allowed to override Discretionary Access Control? */ - if (!(mask & MAY_EXEC) || - (inode->i_mode & S_IXUGO) || S_ISDIR(inode->i_mode)) - if (capable(CAP_DAC_OVERRIDE)) - return 0; - /* Read and search granted if capable(CAP_DAC_READ_SEARCH) */ - if (capable(CAP_DAC_READ_SEARCH) && ((mask == MAY_READ) || - (S_ISDIR(inode->i_mode) && !(mask & MAY_WRITE)))) - return 0; - return -EACCES; + return -EAGAIN; +} + +int +ext3_permission(struct inode *inode, int mask, struct nameidata *nd) +{ + return generic_permission(inode, mask, ext3_check_acl); } /* diff --git a/fs/hfs/inode.c b/fs/hfs/inode.c index 6c869f377964..ae55ce1a5405 100644 --- a/fs/hfs/inode.c +++ b/fs/hfs/inode.c @@ -517,7 +517,7 @@ static int hfs_permission(struct inode *inode, int mask, { if (S_ISREG(inode->i_mode) && mask & MAY_EXEC) return 0; - return vfs_permission(inode, mask); + return generic_permission(inode, mask, NULL); } static int hfs_file_open(struct inode *inode, struct file *file) diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c index eff1c987b6fb..f58025cb38af 100644 --- a/fs/hfsplus/inode.c +++ b/fs/hfsplus/inode.c @@ -260,7 +260,7 @@ static int hfsplus_permission(struct inode *inode, int mask, struct nameidata *n */ if (S_ISREG(inode->i_mode) && mask & MAY_EXEC && !(inode->i_mode & 0111)) return 0; - return vfs_permission(inode, mask); + return generic_permission(inode, mask, NULL); } diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c index 7d42da0a304d..937fac26b34a 100644 --- a/fs/hostfs/hostfs_kern.c +++ b/fs/hostfs/hostfs_kern.c @@ -808,7 +808,7 @@ int hostfs_permission(struct inode *ino, int desired, struct nameidata *nd) if(name == NULL) return(-ENOMEM); err = access_file(name, r, w, x); kfree(name); - if(!err) err = vfs_permission(ino, desired); + if(!err) err = generic_permission(ino, desired, NULL); return(err); } diff --git a/fs/jfs/acl.c b/fs/jfs/acl.c index fc584f2194f4..8d2a9ab981d4 100644 --- a/fs/jfs/acl.c +++ b/fs/jfs/acl.c @@ -123,88 +123,25 @@ out: return rc; } -/* - * jfs_permission() - * - * modified vfs_permission to check posix acl - */ -int jfs_permission(struct inode * inode, int mask, struct nameidata *nd) +static int jfs_check_acl(struct inode *inode, int mask) { - umode_t mode = inode->i_mode; struct jfs_inode_info *ji = JFS_IP(inode); - if (mask & MAY_WRITE) { - /* - * Nobody gets write access to a read-only fs. - */ - if (IS_RDONLY(inode) && - (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) - return -EROFS; - - /* - * Nobody gets write access to an immutable file. - */ - if (IS_IMMUTABLE(inode)) - return -EACCES; - } - - if (current->fsuid == inode->i_uid) { - mode >>= 6; - goto check_mode; - } - /* - * ACL can't contain additional permissions if the ACL_MASK entry - * is zero. - */ - if (!(mode & S_IRWXG)) - goto check_groups; - if (ji->i_acl == JFS_ACL_NOT_CACHED) { - struct posix_acl *acl; - - acl = jfs_get_acl(inode, ACL_TYPE_ACCESS); - + struct posix_acl *acl = jfs_get_acl(inode, ACL_TYPE_ACCESS); if (IS_ERR(acl)) return PTR_ERR(acl); posix_acl_release(acl); } - if (ji->i_acl) { - int rc = posix_acl_permission(inode, ji->i_acl, mask); - if (rc == -EACCES) - goto check_capabilities; - return rc; - } - -check_groups: - if (in_group_p(inode->i_gid)) - mode >>= 3; - -check_mode: - /* - * If the DACs are ok we don't need any capability check. - */ - if (((mode & mask & (MAY_READ|MAY_WRITE|MAY_EXEC)) == mask)) - return 0; + if (ji->i_acl) + return posix_acl_permission(inode, ji->i_acl, mask); + return -EAGAIN; +} -check_capabilities: - /* - * Read/write DACs are always overridable. - * Executable DACs are overridable if at least one exec bit is set. - */ - if (!(mask & MAY_EXEC) || - (inode->i_mode & S_IXUGO) || S_ISDIR(inode->i_mode)) - if (capable(CAP_DAC_OVERRIDE)) - return 0; - - /* - * Searching includes executable on directories, else just read. - */ - if (mask == MAY_READ || (S_ISDIR(inode->i_mode) && !(mask & MAY_WRITE))) - if (capable(CAP_DAC_READ_SEARCH)) - return 0; - - return -EACCES; +int jfs_permission(struct inode *inode, int mask, struct nameidata *nd) +{ + return generic_permission(inode, mask, jfs_check_acl); } int jfs_init_acl(struct inode *inode, struct inode *dir) diff --git a/fs/jfs/super.c b/fs/jfs/super.c index 7c91ccfe382f..ecf1bca73050 100644 --- a/fs/jfs/super.c +++ b/fs/jfs/super.c @@ -403,6 +403,10 @@ static int jfs_fill_super(struct super_block *sb, void *data, int silent) } sbi->flag = flag; +#ifdef CONFIG_JFS_POSIX_ACL + sb->s_flags |= MS_POSIXACL; +#endif + if (newLVSize) { printk(KERN_ERR "resize option for remount only\n"); return -EINVAL; diff --git a/fs/namei.c b/fs/namei.c index bf259a9c5b21..287339bb7b7c 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -152,15 +152,19 @@ char * getname(const char __user * filename) return result; } -/* - * vfs_permission() +/** + * generic_permission - check for access rights on a Posix-like filesystem + * @inode: inode to check access rights for + * @mask: right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC) + * @check_acl: optional callback to check for Posix ACLs * - * is used to check for read/write/execute permissions on a file. + * Used to check for read/write/execute permissions on a file. * We use "fsuid" for this, letting us set arbitrary permissions * for filesystem access without changing the "normal" uids which * are used for other things.. */ -int vfs_permission(struct inode * inode, int mask) +int generic_permission(struct inode *inode, int mask, + int (*check_acl)(struct inode *inode, int mask)) { umode_t mode = inode->i_mode; @@ -181,8 +185,18 @@ int vfs_permission(struct inode * inode, int mask) if (current->fsuid == inode->i_uid) mode >>= 6; - else if (in_group_p(inode->i_gid)) - mode >>= 3; + else { + if (IS_POSIXACL(inode) && (mode & S_IRWXG) && check_acl) { + int error = check_acl(inode, mask); + if (error == -EACCES) + goto check_capabilities; + else if (error != -EAGAIN) + return error; + } + + if (in_group_p(inode->i_gid)) + mode >>= 3; + } /* * If the DACs are ok we don't need any capability check. @@ -190,6 +204,7 @@ int vfs_permission(struct inode * inode, int mask) if (((mode & mask & (MAY_READ|MAY_WRITE|MAY_EXEC)) == mask)) return 0; + check_capabilities: /* * Read/write DACs are always overridable. * Executable DACs are overridable if at least one exec bit is set. @@ -220,7 +235,7 @@ int permission(struct inode * inode,int mask, struct nameidata *nd) if (inode->i_op && inode->i_op->permission) retval = inode->i_op->permission(inode, submask, nd); else - retval = vfs_permission(inode, submask); + retval = generic_permission(inode, submask, NULL); if (retval) return retval; @@ -315,7 +330,7 @@ static struct dentry * cached_lookup(struct dentry * parent, struct qstr * name, /* * Short-cut version of permission(), for calling by * path_walk(), when dcache lock is held. Combines parts - * of permission() and vfs_permission(), and tests ONLY for + * of permission() and generic_permission(), and tests ONLY for * MAY_EXEC permission. * * If appropriate, check DAC only. If not appropriate, or @@ -2456,7 +2471,7 @@ EXPORT_SYMBOL(vfs_follow_link); EXPORT_SYMBOL(vfs_link); EXPORT_SYMBOL(vfs_mkdir); EXPORT_SYMBOL(vfs_mknod); -EXPORT_SYMBOL(vfs_permission); +EXPORT_SYMBOL(generic_permission); EXPORT_SYMBOL(vfs_readlink); EXPORT_SYMBOL(vfs_rename); EXPORT_SYMBOL(vfs_rmdir); diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index 626252aec95a..eac30582ff9e 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -1600,7 +1600,7 @@ int nfs_permission(struct inode *inode, int mask, struct nameidata *nd) return res; out_notsup: nfs_revalidate_inode(NFS_SERVER(inode), inode); - res = vfs_permission(inode, mask); + res = generic_permission(inode, mask, NULL); unlock_kernel(); return res; } diff --git a/fs/proc/base.c b/fs/proc/base.c index 987240398c4a..91060b9921cc 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -473,7 +473,7 @@ out: static int proc_permission(struct inode *inode, int mask, struct nameidata *nd) { - if (vfs_permission(inode, mask) != 0) + if (generic_permission(inode, mask, NULL) != 0) return -EACCES; return proc_check_root(inode); } diff --git a/include/linux/fs.h b/include/linux/fs.h index dc457c3fede2..13e1b4cb7c74 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1339,7 +1339,9 @@ extern sector_t bmap(struct inode *, sector_t); extern int setattr_mask(unsigned int); extern int notify_change(struct dentry *, struct iattr *); extern int permission(struct inode *, int, struct nameidata *); -extern int vfs_permission(struct inode *, int); +extern int generic_permission(struct inode *, int, + int (*check_acl)(struct inode *, int)); + extern int get_write_access(struct inode *); extern int deny_write_access(struct file *); static inline void put_write_access(struct inode * inode) -- cgit v1.2.3 From afead7df5a05118052a238c54285e7119da65831 Mon Sep 17 00:00:00 2001 From: Marcelo Tosatti Date: Mon, 18 Oct 2004 18:05:25 -0700 Subject: [PATCH] Adjust alignment of pagevec structure We can shrink the pagevec structure to cacheline align it. It is used all over VM reclaiming and mpage pagecache read code. Right now it is 140 bytes on 64-bit and 72 bytes on 32-bit. Thats just a little bit more than a power of 2 (which will cacheline align), so shrink it to be aligned: 64 bytes on 32bit and 124bytes on 64-bit. It now occupies two cachelines most of the time instead of three. I changed nr and cold to "unsigned short" because they'll never reach 2 ^ 16. Did some reaim benchmarking on 4way PIII (32byte cacheline), with 512MB RAM: #### stock 2.6.9-rc1-mm4 #### Peak load Test: Maximum Jobs per Minute 4144.44 (average of 3 runs) Quick Convergence Test: Maximum Jobs per Minute 4007.86 (average of 3 runs) Peak load Test: Maximum Jobs per Minute 4207.48 (average of 3 runs) Quick Convergence Test: Maximum Jobs per Minute 3999.28 (average of 3 runs) #### shrink-pagevec ##### Peak load Test: Maximum Jobs per Minute 4717.88 (average of 3 runs) Quick Convergence Test: Maximum Jobs per Minute 4360.59 (average of 3 runs) Peak load Test: Maximum Jobs per Minute 4493.18 (average of 3 runs) Quick Convergence Test: Maximum Jobs per Minute 4327.77 (average of 3 runs) Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- include/linux/pagevec.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/linux/pagevec.h b/include/linux/pagevec.h index e6e43ce82b55..39cca92a8d63 100644 --- a/include/linux/pagevec.h +++ b/include/linux/pagevec.h @@ -5,14 +5,14 @@ * pages. A pagevec is a multipage container which is used for that. */ -#define PAGEVEC_SIZE 16 +#define PAGEVEC_SIZE 15 struct page; struct address_space; struct pagevec { - unsigned nr; - int cold; + unsigned short nr; + unsigned short cold; struct page *pages[PAGEVEC_SIZE]; }; -- cgit v1.2.3 From 03808b3fd055447be48735c802e3322127df3c4d Mon Sep 17 00:00:00 2001 From: Antonino Daplas Date: Mon, 18 Oct 2004 18:05:38 -0700 Subject: [PATCH] fbdev: remove unnecessary banshee_wait_idle from tdfxfb - This patch removes the unnecessary call to banshee_wait_idle() from tdfxfb_copyarea, imageblit and fillrect. Removal of the sync will garner an additional ~20% in scrolling speed. - Removes "inverse" which generates a compile warning if modular. Signed-off-by: Antonino Daplas Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/video/tdfxfb.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/drivers/video/tdfxfb.c b/drivers/video/tdfxfb.c index 503ecc3569e8..263a40288118 100644 --- a/drivers/video/tdfxfb.c +++ b/drivers/video/tdfxfb.c @@ -202,7 +202,6 @@ static unsigned long do_lfb_size(struct tdfx_par *par, unsigned short); */ static int nopan = 0; static int nowrap = 1; // not implemented (yet) -static int inverse = 0; static char *mode_option __initdata = NULL; /* ------------------------------------------------------------------------- @@ -921,7 +920,6 @@ static void tdfxfb_fillrect(struct fb_info *info, const struct fb_fillrect *rect tdfx_outl(par, COMMAND_2D, COMMAND_2D_FILLRECT | (tdfx_rop << 24)); tdfx_outl(par, DSTSIZE, rect->width | (rect->height << 16)); tdfx_outl(par, LAUNCH_2D, rect->dx | (rect->dy << 16)); - banshee_wait_idle(info); } /* @@ -957,7 +955,6 @@ static void tdfxfb_copyarea(struct fb_info *info, const struct fb_copyarea *area tdfx_outl(par, DSTSIZE, area->width | (area->height << 16)); tdfx_outl(par, DSTXY, dx | (dy << 16)); tdfx_outl(par, LAUNCH_2D, sx | (sy << 16)); - banshee_wait_idle(info); } static void tdfxfb_imageblit(struct fb_info *info, const struct fb_image *image) @@ -1025,7 +1022,6 @@ static void tdfxfb_imageblit(struct fb_info *info, const struct fb_image *image) case 2: tdfx_outl(par, LAUNCH_2D,*(u16*)chardata); break; case 3: tdfx_outl(par, LAUNCH_2D,*(u16*)chardata | ((chardata[3]) << 24)); break; } - banshee_wait_idle(info); } #endif /* CONFIG_FB_3DFX_ACCEL */ @@ -1397,10 +1393,7 @@ void tdfxfb_setup(char *options) while ((this_opt = strsep(&options, ",")) != NULL) { if (!*this_opt) continue; - if (!strcmp(this_opt, "inverse")) { - inverse = 1; - fb_invert_cmaps(); - } else if(!strcmp(this_opt, "nopan")) { + if(!strcmp(this_opt, "nopan")) { nopan = 1; } else if(!strcmp(this_opt, "nowrap")) { nowrap = 1; -- cgit v1.2.3 From 54f5dd97f7b152f4996c5890a8a6813a37d9d25b Mon Sep 17 00:00:00 2001 From: Antonino Daplas Date: Mon, 18 Oct 2004 18:05:50 -0700 Subject: [PATCH] fbdev: fix logo drawing failure for vga16fb This fixes the logo failing to draw in vga16fb due to faulty boolean logic. Signed-off-by: Antonino Daplas Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/video/vga16fb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/video/vga16fb.c b/drivers/video/vga16fb.c index 2e132fb61562..f27524cb61c2 100644 --- a/drivers/video/vga16fb.c +++ b/drivers/video/vga16fb.c @@ -1306,7 +1306,7 @@ void vga16fb_imageblit(struct fb_info *info, const struct fb_image *image) { if (image->depth == 1) vga_imageblit_expand(info, image); - else if (image->depth <= info->var.bits_per_pixel) + else vga_imageblit_color(info, image); } -- cgit v1.2.3 From 899a94d126cb9ede8b55a168f72e1b7ab5e00c92 Mon Sep 17 00:00:00 2001 From: Antonino Daplas Date: Mon, 18 Oct 2004 18:06:02 -0700 Subject: [PATCH] fbcon: Fix setup boot options of fbcon This patch fixes the 'fbcon=map: