Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
L
linux
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
linux
Commits
bfa414e2
Commit
bfa414e2
authored
Sep 16, 2002
by
Greg Kroah-Hartman
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
USB: Convert the core code to use struct device_driver.
parent
7c3ccad2
Changes
8
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
289 additions
and
559 deletions
+289
-559
drivers/usb/core/devices.c
drivers/usb/core/devices.c
+0
-1
drivers/usb/core/devio.c
drivers/usb/core/devio.c
+17
-44
drivers/usb/core/hcd.c
drivers/usb/core/hcd.c
+3
-5
drivers/usb/core/hcd.h
drivers/usb/core/hcd.h
+1
-7
drivers/usb/core/hub.c
drivers/usb/core/hub.c
+78
-82
drivers/usb/core/hub.h
drivers/usb/core/hub.h
+1
-1
drivers/usb/core/usb.c
drivers/usb/core/usb.c
+172
-393
include/linux/usb.h
include/linux/usb.h
+17
-26
No files found.
drivers/usb/core/devices.c
View file @
bfa414e2
...
...
@@ -111,7 +111,6 @@ static char *format_endpt =
/*
* Need access to the driver and USB bus lists.
* extern struct list_head usb_driver_list;
* extern struct list_head usb_bus_list;
* However, these will come from functions that return ptrs to each of them.
*/
...
...
drivers/usb/core/devio.c
View file @
bfa414e2
...
...
@@ -298,15 +298,15 @@ static void destroy_all_async(struct dev_state *ps)
* they're also undone when devices disconnect.
*/
static
void
*
driver_probe
(
struct
usb_device
*
dev
,
unsigned
int
intf
,
static
int
driver_probe
(
struct
usb_interface
*
intf
,
const
struct
usb_device_id
*
id
)
{
return
NULL
;
return
-
ENODEV
;
}
static
void
driver_disconnect
(
struct
usb_
device
*
dev
,
void
*
context
)
static
void
driver_disconnect
(
struct
usb_
interface
*
intf
)
{
struct
dev_state
*
ps
=
(
struct
dev_state
*
)
context
;
struct
dev_state
*
ps
=
dev_get_drvdata
(
&
intf
->
dev
)
;
if
(
!
ps
)
return
;
...
...
@@ -317,6 +317,7 @@ static void driver_disconnect(struct usb_device *dev, void *context)
/* prevent new I/O requests */
ps
->
dev
=
0
;
ps
->
ifclaimed
=
0
;
dev_set_drvdata
(
&
intf
->
dev
,
NULL
);
/* force async requests to complete */
destroy_all_async
(
ps
);
...
...
@@ -427,30 +428,6 @@ static int findintfif(struct usb_device *dev, unsigned int ifn)
return
-
ENOENT
;
}
extern
struct
list_head
usb_driver_list
;
#if 0
static int finddriver(struct usb_driver **driver, char *name)
{
struct list_head *tmp;
tmp = usb_driver_list.next;
while (tmp != &usb_driver_list) {
struct usb_driver *d = list_entry(tmp, struct usb_driver,
driver_list);
if (!strcmp(d->name, name)) {
*driver = d;
return 0;
}
tmp = tmp->next;
}
return -EINVAL;
}
#endif
static
int
check_ctrlrecip
(
struct
dev_state
*
ps
,
unsigned
int
requesttype
,
unsigned
int
index
)
{
int
ret
;
...
...
@@ -723,11 +700,10 @@ static int proc_resetdevice(struct dev_state *ps)
if
(
test_bit
(
i
,
&
ps
->
ifclaimed
))
continue
;
lock_kernel
(
);
err
(
"%s - this function is broken"
,
__FUNCTION__
);
if
(
intf
->
driver
&&
ps
->
dev
)
{
usb_
bind_driver
(
intf
->
driver
,
intf
);
usb_
device_probe
(
&
intf
->
dev
);
}
unlock_kernel
();
}
return
0
;
...
...
@@ -1090,22 +1066,19 @@ static int proc_ioctl (struct dev_state *ps, void *arg)
/* disconnect kernel driver from interface, leaving it unbound. */
case
USBDEVFS_DISCONNECT
:
/* this function is voodoo. without locking it is a maybe thing */
lock_kernel
();
/* this function is voodoo. */
driver
=
ifp
->
driver
;
if
(
driver
)
{
dbg
(
"disconnect '%s' from dev %d interface %d"
,
driver
->
name
,
ps
->
dev
->
devnum
,
ctrl
.
ifno
);
usb_unbind_driver
(
ps
->
dev
,
ifp
);
usb_driver_release_interface
(
driver
,
ifp
);
usb_device_remove
(
&
ifp
->
dev
);
}
else
retval
=
-
EINVAL
;
unlock_kernel
();
break
;
/* let kernel drivers try to (re)bind to the interface */
case
USBDEVFS_CONNECT
:
usb_find_interface_driver
(
ps
->
dev
,
ifp
);
retval
=
usb_device_probe
(
&
ifp
->
dev
);
break
;
/* talk directly to the interface's driver */
...
...
drivers/usb/core/hcd.c
View file @
bfa414e2
...
...
@@ -722,12 +722,10 @@ int usb_register_root_hub (struct usb_device *usb_dev, struct device *parent_dev
{
int
retval
;
usb_dev
->
dev
.
parent
=
parent_dev
;
strcpy
(
&
usb_dev
->
dev
.
name
[
0
],
"usb_name"
);
strcpy
(
&
usb_dev
->
dev
.
bus_id
[
0
],
"usb_bus"
);
retval
=
usb_new_device
(
usb_dev
);
sprintf
(
&
usb_dev
->
dev
.
bus_id
[
0
],
"usb%d"
,
usb_dev
->
bus
->
busnum
);
retval
=
usb_new_device
(
usb_dev
,
parent_dev
);
if
(
retval
)
put_device
(
&
usb_dev
->
dev
);
err
(
"%s - usb_new_device failed with value %d"
,
__FUNCTION__
,
retval
);
return
retval
;
}
EXPORT_SYMBOL
(
usb_register_root_hub
);
...
...
drivers/usb/core/hcd.h
View file @
bfa414e2
...
...
@@ -270,7 +270,7 @@ extern void usb_hc_died (struct usb_hcd *hcd);
/* -------------------------------------------------------------------------- */
/* Enumeration is only for the hub driver, or HCD virtual root hubs */
extern
int
usb_new_device
(
struct
usb_device
*
dev
);
extern
int
usb_new_device
(
struct
usb_device
*
dev
,
struct
device
*
parent
);
extern
void
usb_connect
(
struct
usb_device
*
dev
);
extern
void
usb_disconnect
(
struct
usb_device
**
);
...
...
@@ -396,12 +396,6 @@ extern int usb_find_interface_driver (struct usb_device *dev,
#define usb_endpoint_out(ep_dir) (!((ep_dir) & USB_DIR_IN))
/* for probe/disconnect with correct module usage counting */
void
*
usb_bind_driver
(
struct
usb_driver
*
driver
,
struct
usb_interface
*
intf
);
void
usb_unbind_driver
(
struct
usb_device
*
device
,
struct
usb_interface
*
intf
);
extern
struct
list_head
usb_driver_list
;
/*
* USB device fs stuff
*/
...
...
drivers/usb/core/hub.c
View file @
bfa414e2
...
...
@@ -175,6 +175,7 @@ static void hub_tt_kevent (void *arg)
while
(
!
list_empty
(
&
hub
->
tt
.
clear_list
))
{
struct
list_head
*
temp
;
struct
usb_tt_clear
*
clear
;
struct
usb_device
*
dev
;
int
status
;
temp
=
hub
->
tt
.
clear_list
.
next
;
...
...
@@ -183,13 +184,13 @@ static void hub_tt_kevent (void *arg)
/* drop lock so HCD can concurrently report other TT errors */
spin_unlock_irqrestore
(
&
hub
->
tt
.
lock
,
flags
);
status
=
hub_clear_tt_buffer
(
hub
->
dev
,
clear
->
devinfo
,
clear
->
tt
);
dev
=
interface_to_usbdev
(
hub
->
intf
);
status
=
hub_clear_tt_buffer
(
dev
,
clear
->
devinfo
,
clear
->
tt
);
spin_lock_irqsave
(
&
hub
->
tt
.
lock
,
flags
);
if
(
status
)
err
(
"usb-%s-%s clear tt %d (%04x) error %d"
,
hub
->
dev
->
bus
->
bus_name
,
hub
->
dev
->
devpath
,
dev
->
bus
->
bus_name
,
dev
->
devpath
,
clear
->
tt
,
clear
->
devinfo
,
status
);
kfree
(
clear
);
}
...
...
@@ -245,12 +246,14 @@ void usb_hub_tt_clear_buffer (struct usb_device *dev, int pipe)
static
void
usb_hub_power_on
(
struct
usb_hub
*
hub
)
{
struct
usb_device
*
dev
;
int
i
;
/* Enable power to the ports */
dbg
(
"enabling power on all ports"
);
dev
=
interface_to_usbdev
(
hub
->
intf
);
for
(
i
=
0
;
i
<
hub
->
descriptor
->
bNbrPorts
;
i
++
)
usb_set_port_feature
(
hub
->
dev
,
i
+
1
,
USB_PORT_FEAT_POWER
);
usb_set_port_feature
(
dev
,
i
+
1
,
USB_PORT_FEAT_POWER
);
/* Wait for power to be enabled */
wait_ms
(
hub
->
descriptor
->
bPwrOn2PwrGood
*
2
);
...
...
@@ -259,7 +262,7 @@ static void usb_hub_power_on(struct usb_hub *hub)
static
int
usb_hub_configure
(
struct
usb_hub
*
hub
,
struct
usb_endpoint_descriptor
*
endpoint
)
{
struct
usb_device
*
dev
=
hub
->
dev
;
struct
usb_device
*
dev
=
interface_to_usbdev
(
hub
->
intf
)
;
struct
usb_hub_status
hubstatus
;
unsigned
int
pipe
;
int
maxp
,
ret
;
...
...
@@ -425,39 +428,81 @@ static int usb_hub_configure(struct usb_hub *hub,
return
0
;
}
static
void
*
hub_probe
(
struct
usb_device
*
dev
,
unsigned
int
i
,
const
struct
usb_device_id
*
id
)
static
void
hub_disconnect
(
struct
usb_interface
*
intf
)
{
struct
usb_hub
*
hub
=
dev_get_drvdata
(
&
intf
->
dev
);
unsigned
long
flags
;
if
(
!
hub
)
return
;
dev_set_drvdata
(
&
intf
->
dev
,
NULL
);
spin_lock_irqsave
(
&
hub_event_lock
,
flags
);
/* Delete it and then reset it */
list_del
(
&
hub
->
event_list
);
INIT_LIST_HEAD
(
&
hub
->
event_list
);
list_del
(
&
hub
->
hub_list
);
INIT_LIST_HEAD
(
&
hub
->
hub_list
);
spin_unlock_irqrestore
(
&
hub_event_lock
,
flags
);
down
(
&
hub
->
khubd_sem
);
/* Wait for khubd to leave this hub alone. */
up
(
&
hub
->
khubd_sem
);
/* assuming we used keventd, it must quiesce too */
if
(
hub
->
tt
.
hub
)
flush_scheduled_tasks
();
if
(
hub
->
urb
)
{
usb_unlink_urb
(
hub
->
urb
);
usb_free_urb
(
hub
->
urb
);
hub
->
urb
=
NULL
;
}
if
(
hub
->
descriptor
)
{
kfree
(
hub
->
descriptor
);
hub
->
descriptor
=
NULL
;
}
/* Free the memory */
kfree
(
hub
);
}
static
int
hub_probe
(
struct
usb_interface
*
intf
,
const
struct
usb_device_id
*
id
)
{
struct
usb_interface_descriptor
*
interface
;
struct
usb_interface_descriptor
*
desc
;
struct
usb_endpoint_descriptor
*
endpoint
;
struct
usb_device
*
dev
;
struct
usb_hub
*
hub
;
unsigned
long
flags
;
interface
=
&
dev
->
actconfig
->
interface
[
i
].
altsetting
[
0
];
desc
=
intf
->
altsetting
+
intf
->
act_altsetting
;
dev
=
interface_to_usbdev
(
intf
);
/* Some hubs have a subclass of 1, which AFAICT according to the */
/* specs is not defined, but it works */
if
((
interface
->
bInterfaceSubClass
!=
0
)
&&
(
interface
->
bInterfaceSubClass
!=
1
))
{
if
((
desc
->
bInterfaceSubClass
!=
0
)
&&
(
desc
->
bInterfaceSubClass
!=
1
))
{
err
(
"invalid subclass (%d) for USB hub device #%d"
,
interface
->
bInterfaceSubClass
,
dev
->
devnum
);
return
NULL
;
desc
->
bInterfaceSubClass
,
dev
->
devnum
);
return
-
EIO
;
}
/* Multiple endpoints? What kind of mutant ninja-hub is this? */
if
(
interface
->
bNumEndpoints
!=
1
)
{
if
(
desc
->
bNumEndpoints
!=
1
)
{
err
(
"invalid bNumEndpoints (%d) for USB hub device #%d"
,
interface
->
bNumEndpoints
,
dev
->
devnum
);
return
NULL
;
desc
->
bNumEndpoints
,
dev
->
devnum
);
return
-
EIO
;
}
endpoint
=
&
interface
->
endpoint
[
0
];
endpoint
=
&
desc
->
endpoint
[
0
];
/* Output endpoint? Curiousier and curiousier.. */
if
(
!
(
endpoint
->
bEndpointAddress
&
USB_DIR_IN
))
{
err
(
"Device #%d is hub class, but has output endpoint?"
,
dev
->
devnum
);
return
NULL
;
return
-
EIO
;
}
/* If it's not an interrupt endpoint, we'd better punt! */
...
...
@@ -465,7 +510,7 @@ static void *hub_probe(struct usb_device *dev, unsigned int i,
!=
USB_ENDPOINT_XFER_INT
)
{
err
(
"Device #%d is hub class, but endpoint is not interrupt?"
,
dev
->
devnum
);
return
NULL
;
return
-
EIO
;
}
/* We found a hub */
...
...
@@ -474,13 +519,13 @@ static void *hub_probe(struct usb_device *dev, unsigned int i,
hub
=
kmalloc
(
sizeof
(
*
hub
),
GFP_KERNEL
);
if
(
!
hub
)
{
err
(
"couldn't kmalloc hub struct"
);
return
NULL
;
return
-
ENOMEM
;
}
memset
(
hub
,
0
,
sizeof
(
*
hub
));
INIT_LIST_HEAD
(
&
hub
->
event_list
);
hub
->
dev
=
dev
;
hub
->
intf
=
intf
;
init_MUTEX
(
&
hub
->
khubd_sem
);
/* Record the new hub's existence */
...
...
@@ -489,65 +534,17 @@ static void *hub_probe(struct usb_device *dev, unsigned int i,
list_add
(
&
hub
->
hub_list
,
&
hub_list
);
spin_unlock_irqrestore
(
&
hub_event_lock
,
flags
);
dev_set_drvdata
(
&
intf
->
dev
,
hub
);
if
(
usb_hub_configure
(
hub
,
endpoint
)
>=
0
)
{
strcpy
(
dev
->
actconfig
->
interface
[
i
].
dev
.
name
,
"Hub/Port Status Changes"
);
return
hub
;
strcpy
(
intf
->
dev
.
name
,
"Hub/Port Status Changes"
);
return
0
;
}
err
(
"hub configuration failed for device at %s"
,
dev
->
devpath
);
/* free hub, but first clean up its list. */
spin_lock_irqsave
(
&
hub_event_lock
,
flags
);
/* Delete it and then reset it */
list_del
(
&
hub
->
event_list
);
INIT_LIST_HEAD
(
&
hub
->
event_list
);
list_del
(
&
hub
->
hub_list
);
INIT_LIST_HEAD
(
&
hub
->
hub_list
);
spin_unlock_irqrestore
(
&
hub_event_lock
,
flags
);
kfree
(
hub
);
return
NULL
;
}
static
void
hub_disconnect
(
struct
usb_device
*
dev
,
void
*
ptr
)
{
struct
usb_hub
*
hub
=
(
struct
usb_hub
*
)
ptr
;
unsigned
long
flags
;
spin_lock_irqsave
(
&
hub_event_lock
,
flags
);
/* Delete it and then reset it */
list_del
(
&
hub
->
event_list
);
INIT_LIST_HEAD
(
&
hub
->
event_list
);
list_del
(
&
hub
->
hub_list
);
INIT_LIST_HEAD
(
&
hub
->
hub_list
);
spin_unlock_irqrestore
(
&
hub_event_lock
,
flags
);
down
(
&
hub
->
khubd_sem
);
/* Wait for khubd to leave this hub alone. */
up
(
&
hub
->
khubd_sem
);
/* assuming we used keventd, it must quiesce too */
if
(
hub
->
tt
.
hub
)
flush_scheduled_tasks
();
if
(
hub
->
urb
)
{
usb_unlink_urb
(
hub
->
urb
);
usb_free_urb
(
hub
->
urb
);
hub
->
urb
=
NULL
;
}
if
(
hub
->
descriptor
)
{
kfree
(
hub
->
descriptor
);
hub
->
descriptor
=
NULL
;
}
/* Free the memory */
kfree
(
hub
);
hub_disconnect
(
intf
);
return
-
ENODEV
;
}
static
int
hub_ioctl
(
struct
usb_device
*
hub
,
unsigned
int
code
,
void
*
user_data
)
...
...
@@ -584,7 +581,7 @@ static int hub_ioctl(struct usb_device *hub, unsigned int code, void *user_data)
static
int
usb_hub_reset
(
struct
usb_hub
*
hub
)
{
struct
usb_device
*
dev
=
hub
->
dev
;
struct
usb_device
*
dev
=
interface_to_usbdev
(
hub
->
intf
)
;
int
i
;
/* Disconnect any attached devices */
...
...
@@ -796,7 +793,7 @@ static int usb_hub_port_debounce(struct usb_device *hub, int port)
static
void
usb_hub_port_connect_change
(
struct
usb_hub
*
hubstate
,
int
port
,
u16
portstatus
,
u16
portchange
)
{
struct
usb_device
*
hub
=
hubstate
->
dev
;
struct
usb_device
*
hub
=
interface_to_usbdev
(
hubstate
->
intf
)
;
struct
usb_device
*
dev
;
unsigned
int
delay
=
HUB_SHORT_RESET_TIME
;
int
i
;
...
...
@@ -891,11 +888,10 @@ static void usb_hub_port_connect_change(struct usb_hub *hubstate, int port,
/* put the device in the global device tree. the hub port
* is the "bus_id"; hubs show in hierarchy like bridges
*/
dev
->
dev
.
parent
=
&
dev
->
parent
->
dev
;
sprintf
(
&
dev
->
dev
.
bus_id
[
0
],
"%d"
,
port
+
1
);
dev
->
dev
.
parent
=
dev
->
parent
->
dev
.
parent
->
parent
;
/* Run it through the hoops (find a driver, etc) */
if
(
!
usb_new_device
(
dev
))
if
(
!
usb_new_device
(
dev
,
&
hub
->
dev
))
goto
done
;
/* Free the configuration if there was an error */
...
...
@@ -940,7 +936,7 @@ static void usb_hub_events(void)
tmp
=
hub_event_list
.
next
;
hub
=
list_entry
(
tmp
,
struct
usb_hub
,
event_list
);
dev
=
hub
->
dev
;
dev
=
interface_to_usbdev
(
hub
->
intf
)
;
list_del
(
tmp
);
INIT_LIST_HEAD
(
tmp
);
...
...
@@ -1081,8 +1077,8 @@ MODULE_DEVICE_TABLE (usb, hub_id_table);
static
struct
usb_driver
hub_driver
=
{
.
name
=
"hub"
,
.
probe
=
hub_probe
,
.
ioctl
=
hub_ioctl
,
.
disconnect
=
hub_disconnect
,
.
ioctl
=
hub_ioctl
,
.
id_table
=
hub_id_table
,
};
...
...
drivers/usb/core/hub.h
View file @
bfa414e2
...
...
@@ -170,7 +170,7 @@ struct usb_tt_clear {
extern
void
usb_hub_tt_clear_buffer
(
struct
usb_device
*
dev
,
int
pipe
);
struct
usb_hub
{
struct
usb_
device
*
dev
;
/* the "real" device */
struct
usb_
interface
*
intf
;
/* the "real" device */
struct
urb
*
urb
;
/* for interrupt polling pipe */
/* buffer for urb ... 1 bit each for hub and children, rounded up */
...
...
drivers/usb/core/usb.c
View file @
bfa414e2
This diff is collapsed.
Click to expand it.
include/linux/usb.h
View file @
bfa414e2
...
...
@@ -399,9 +399,6 @@ extern struct usb_device *usb_get_dev(struct usb_device *dev);
extern
void
usb_free_dev
(
struct
usb_device
*
);
#define usb_put_dev usb_free_dev
/* for when layers above USB add new non-USB drivers */
extern
void
usb_scan_devices
(
void
);
/* mostly for devices emulating SCSI over USB */
extern
int
usb_reset_device
(
struct
usb_device
*
dev
);
...
...
@@ -623,10 +620,10 @@ struct usb_device_id {
* expose information to user space regardless of where they
* do (or don't) show up otherwise in the filesystem.
* @id_table: USB drivers use ID table to support hotplugging.
* Export this with MODULE_DEVICE_TABLE(usb,...)
, or use NULL to
*
say that probe() should be called for any unclaimed interfce.
* Export this with MODULE_DEVICE_TABLE(usb,...)
. This must be set
*
or your driver's probe function will never get called.
*
* USB drivers
should
provide a name, probe() and disconnect() methods,
* USB drivers
must
provide a name, probe() and disconnect() methods,
* and an id_table. Other driver fields are optional.
*
* The id_table is used in hotplugging. It holds a set of descriptors,
...
...
@@ -643,32 +640,23 @@ struct usb_device_id {
*/
struct
usb_driver
{
struct
module
*
owner
;
const
char
*
name
;
void
*
(
*
probe
)(
struct
usb_device
*
dev
,
/* the device */
unsigned
intf
,
/* what interface */
const
struct
usb_device_id
*
id
/* from id_table */
);
void
(
*
disconnect
)(
struct
usb_device
*
dev
,
/* the device */
void
*
handle
/* as returned by probe() */
);
struct
list_head
driver_list
;
struct
semaphore
serialize
;
int
(
*
probe
)
(
struct
usb_interface
*
intf
,
const
struct
usb_device_id
*
id
);
/* ioctl -- userspace apps can talk to drivers through usbfs */
int
(
*
ioctl
)(
struct
usb_device
*
dev
,
unsigned
int
code
,
void
*
buf
);
void
(
*
disconnect
)
(
struct
usb_interface
*
intf
);
int
(
*
ioctl
)
(
struct
usb_device
*
dev
,
unsigned
int
code
,
void
*
buf
);
/* support for "new-style" USB hotplugging */
const
struct
usb_device_id
*
id_table
;
/* suspend before the bus suspends;
* disconnect or resume when the bus resumes */
/* void (*suspend)(struct usb_device *dev); */
/* void (*resume)(struct usb_device *dev); */
struct
device_driver
driver
;
struct
semaphore
serialize
;
};
#define to_usb_driver(d) container_of(d, struct usb_driver, driver)
extern
struct
bus_type
usb_bus_type
;
...
...
@@ -682,6 +670,9 @@ extern void usb_deregister(struct usb_driver *);
extern
int
usb_register_dev
(
struct
file_operations
*
fops
,
int
minor
,
int
num_minors
,
int
*
start_minor
);
extern
void
usb_deregister_dev
(
int
num_minors
,
int
start_minor
);
extern
int
usb_device_probe
(
struct
device
*
dev
);
extern
int
usb_device_remove
(
struct
device
*
dev
);
/* -------------------------------------------------------------------------- */
/*
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment