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
621de593
Commit
621de593
authored
Mar 30, 2009
by
Jiri Kosina
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'autosuspend' into for-next
Conflicts: drivers/hid/hid-core.c
parents
afa5eb7c
6d779768
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
428 additions
and
114 deletions
+428
-114
drivers/hid/hid-core.c
drivers/hid/hid-core.c
+16
-0
drivers/hid/hidraw.c
drivers/hid/hidraw.c
+14
-3
drivers/hid/usbhid/hid-core.c
drivers/hid/usbhid/hid-core.c
+367
-105
drivers/hid/usbhid/hiddev.c
drivers/hid/usbhid/hiddev.c
+15
-2
drivers/hid/usbhid/usbhid.h
drivers/hid/usbhid/usbhid.h
+10
-4
include/linux/hid.h
include/linux/hid.h
+6
-0
No files found.
drivers/hid/hid-core.c
View file @
621de593
...
...
@@ -1819,6 +1819,22 @@ void hid_unregister_driver(struct hid_driver *hdrv)
}
EXPORT_SYMBOL_GPL
(
hid_unregister_driver
);
int
hid_check_keys_pressed
(
struct
hid_device
*
hid
)
{
struct
hid_input
*
hidinput
;
int
i
;
list_for_each_entry
(
hidinput
,
&
hid
->
inputs
,
list
)
{
for
(
i
=
0
;
i
<
BITS_TO_LONGS
(
KEY_MAX
);
i
++
)
if
(
hidinput
->
input
->
key
[
i
])
return
1
;
}
return
0
;
}
EXPORT_SYMBOL_GPL
(
hid_check_keys_pressed
);
static
int
__init
hid_init
(
void
)
{
int
ret
;
...
...
drivers/hid/hidraw.c
View file @
621de593
...
...
@@ -181,9 +181,17 @@ static int hidraw_open(struct inode *inode, struct file *file)
dev
=
hidraw_table
[
minor
];
if
(
!
dev
->
open
++
)
{
if
(
dev
->
hid
->
ll_driver
->
power
)
{
err
=
dev
->
hid
->
ll_driver
->
power
(
dev
->
hid
,
PM_HINT_FULLON
);
if
(
err
<
0
)
goto
out_unlock
;
}
err
=
dev
->
hid
->
ll_driver
->
open
(
dev
->
hid
);
if
(
err
<
0
)
if
(
err
<
0
)
{
if
(
dev
->
hid
->
ll_driver
->
power
)
dev
->
hid
->
ll_driver
->
power
(
dev
->
hid
,
PM_HINT_NORMAL
);
dev
->
open
--
;
}
}
out_unlock:
...
...
@@ -209,10 +217,13 @@ static int hidraw_release(struct inode * inode, struct file * file)
list_del
(
&
list
->
node
);
dev
=
hidraw_table
[
minor
];
if
(
!--
dev
->
open
)
{
if
(
list
->
hidraw
->
exist
)
if
(
list
->
hidraw
->
exist
)
{
if
(
dev
->
hid
->
ll_driver
->
power
)
dev
->
hid
->
ll_driver
->
power
(
dev
->
hid
,
PM_HINT_NORMAL
);
dev
->
hid
->
ll_driver
->
close
(
dev
->
hid
);
else
}
else
{
kfree
(
list
->
hidraw
);
}
}
kfree
(
list
);
...
...
drivers/hid/usbhid/hid-core.c
View file @
621de593
...
...
@@ -5,6 +5,7 @@
* Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
* Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
* Copyright (c) 2006-2008 Jiri Kosina
* Copyright (c) 2007-2008 Oliver Neukum
*/
/*
...
...
@@ -27,6 +28,7 @@
#include <asm/byteorder.h>
#include <linux/input.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
#include <linux/usb.h>
...
...
@@ -53,6 +55,10 @@ static unsigned int hid_mousepoll_interval;
module_param_named
(
mousepoll
,
hid_mousepoll_interval
,
uint
,
0644
);
MODULE_PARM_DESC
(
mousepoll
,
"Polling interval of mice"
);
static
unsigned
int
ignoreled
;
module_param_named
(
ignoreled
,
ignoreled
,
uint
,
0644
);
MODULE_PARM_DESC
(
ignoreled
,
"Autosuspend with active leds"
);
/* Quirks specified at module load time */
static
char
*
quirks_param
[
MAX_USBHID_BOOT_QUIRKS
]
=
{
[
0
...
(
MAX_USBHID_BOOT_QUIRKS
-
1
)
]
=
NULL
};
module_param_array_named
(
quirks
,
quirks_param
,
charp
,
NULL
,
0444
);
...
...
@@ -63,8 +69,13 @@ MODULE_PARM_DESC(quirks, "Add/modify USB HID quirks by specifying "
/*
* Input submission and I/O error handler.
*/
static
DEFINE_MUTEX
(
hid_open_mut
);
static
struct
workqueue_struct
*
resumption_waker
;
static
void
hid_io_error
(
struct
hid_device
*
hid
);
static
int
hid_submit_out
(
struct
hid_device
*
hid
);
static
int
hid_submit_ctrl
(
struct
hid_device
*
hid
);
static
void
hid_cancel_delayed_stuff
(
struct
usbhid_device
*
usbhid
);
/* Start up the input URB */
static
int
hid_start_in
(
struct
hid_device
*
hid
)
...
...
@@ -73,15 +84,16 @@ static int hid_start_in(struct hid_device *hid)
int
rc
=
0
;
struct
usbhid_device
*
usbhid
=
hid
->
driver_data
;
spin_lock_irqsave
(
&
usbhid
->
in
lock
,
flags
);
if
(
hid
->
open
>
0
&&
!
test_bit
(
HID_SUSPENDED
,
&
usbhid
->
iofl
)
&&
spin_lock_irqsave
(
&
usbhid
->
lock
,
flags
);
if
(
hid
->
open
>
0
&&
!
test_bit
(
HID_DISCONNECTED
,
&
usbhid
->
iofl
)
&&
!
test_bit
(
HID_REPORTED_IDLE
,
&
usbhid
->
iofl
)
&&
!
test_and_set_bit
(
HID_IN_RUNNING
,
&
usbhid
->
iofl
))
{
rc
=
usb_submit_urb
(
usbhid
->
urbin
,
GFP_ATOMIC
);
if
(
rc
!=
0
)
clear_bit
(
HID_IN_RUNNING
,
&
usbhid
->
iofl
);
}
spin_unlock_irqrestore
(
&
usbhid
->
in
lock
,
flags
);
spin_unlock_irqrestore
(
&
usbhid
->
lock
,
flags
);
return
rc
;
}
...
...
@@ -145,7 +157,7 @@ static void hid_io_error(struct hid_device *hid)
unsigned
long
flags
;
struct
usbhid_device
*
usbhid
=
hid
->
driver_data
;
spin_lock_irqsave
(
&
usbhid
->
in
lock
,
flags
);
spin_lock_irqsave
(
&
usbhid
->
lock
,
flags
);
/* Stop when disconnected */
if
(
test_bit
(
HID_DISCONNECTED
,
&
usbhid
->
iofl
))
...
...
@@ -175,7 +187,51 @@ static void hid_io_error(struct hid_device *hid)
mod_timer
(
&
usbhid
->
io_retry
,
jiffies
+
msecs_to_jiffies
(
usbhid
->
retry_delay
));
done:
spin_unlock_irqrestore
(
&
usbhid
->
inlock
,
flags
);
spin_unlock_irqrestore
(
&
usbhid
->
lock
,
flags
);
}
static
void
usbhid_mark_busy
(
struct
usbhid_device
*
usbhid
)
{
struct
usb_interface
*
intf
=
usbhid
->
intf
;
usb_mark_last_busy
(
interface_to_usbdev
(
intf
));
}
static
int
usbhid_restart_out_queue
(
struct
usbhid_device
*
usbhid
)
{
struct
hid_device
*
hid
=
usb_get_intfdata
(
usbhid
->
intf
);
int
kicked
;
if
(
!
hid
)
return
0
;
if
((
kicked
=
(
usbhid
->
outhead
!=
usbhid
->
outtail
)))
{
dbg
(
"Kicking head %d tail %d"
,
usbhid
->
outhead
,
usbhid
->
outtail
);
if
(
hid_submit_out
(
hid
))
{
clear_bit
(
HID_OUT_RUNNING
,
&
usbhid
->
iofl
);
wake_up
(
&
usbhid
->
wait
);
}
}
return
kicked
;
}
static
int
usbhid_restart_ctrl_queue
(
struct
usbhid_device
*
usbhid
)
{
struct
hid_device
*
hid
=
usb_get_intfdata
(
usbhid
->
intf
);
int
kicked
;
WARN_ON
(
hid
==
NULL
);
if
(
!
hid
)
return
0
;
if
((
kicked
=
(
usbhid
->
ctrlhead
!=
usbhid
->
ctrltail
)))
{
dbg
(
"Kicking head %d tail %d"
,
usbhid
->
ctrlhead
,
usbhid
->
ctrltail
);
if
(
hid_submit_ctrl
(
hid
))
{
clear_bit
(
HID_CTRL_RUNNING
,
&
usbhid
->
iofl
);
wake_up
(
&
usbhid
->
wait
);
}
}
return
kicked
;
}
/*
...
...
@@ -190,12 +246,23 @@ static void hid_irq_in(struct urb *urb)
switch
(
urb
->
status
)
{
case
0
:
/* success */
usbhid_mark_busy
(
usbhid
);
usbhid
->
retry_delay
=
0
;
hid_input_report
(
urb
->
context
,
HID_INPUT_REPORT
,
urb
->
transfer_buffer
,
urb
->
actual_length
,
1
);
/*
* autosuspend refused while keys are pressed
* because most keyboards don't wake up when
* a key is released
*/
if
(
hid_check_keys_pressed
(
hid
))
set_bit
(
HID_KEYS_PRESSED
,
&
usbhid
->
iofl
);
else
clear_bit
(
HID_KEYS_PRESSED
,
&
usbhid
->
iofl
);
break
;
case
-
EPIPE
:
/* stall */
usbhid_mark_busy
(
usbhid
);
clear_bit
(
HID_IN_RUNNING
,
&
usbhid
->
iofl
);
set_bit
(
HID_CLEAR_HALT
,
&
usbhid
->
iofl
);
schedule_work
(
&
usbhid
->
reset_work
);
...
...
@@ -209,6 +276,7 @@ static void hid_irq_in(struct urb *urb)
case
-
EPROTO
:
/* protocol error or unplug */
case
-
ETIME
:
/* protocol error or unplug */
case
-
ETIMEDOUT
:
/* Should never happen, but... */
usbhid_mark_busy
(
usbhid
);
clear_bit
(
HID_IN_RUNNING
,
&
usbhid
->
iofl
);
hid_io_error
(
hid
);
return
;
...
...
@@ -239,16 +307,25 @@ static int hid_submit_out(struct hid_device *hid)
report
=
usbhid
->
out
[
usbhid
->
outtail
].
report
;
raw_report
=
usbhid
->
out
[
usbhid
->
outtail
].
raw_report
;
usbhid
->
urbout
->
transfer_buffer_length
=
((
report
->
size
-
1
)
>>
3
)
+
1
+
(
report
->
id
>
0
);
usbhid
->
urbout
->
dev
=
hid_to_usb_dev
(
hid
);
memcpy
(
usbhid
->
outbuf
,
raw_report
,
usbhid
->
urbout
->
transfer_buffer_length
);
kfree
(
raw_report
);
if
(
!
test_bit
(
HID_REPORTED_IDLE
,
&
usbhid
->
iofl
))
{
usbhid
->
urbout
->
transfer_buffer_length
=
((
report
->
size
-
1
)
>>
3
)
+
1
+
(
report
->
id
>
0
);
usbhid
->
urbout
->
dev
=
hid_to_usb_dev
(
hid
);
memcpy
(
usbhid
->
outbuf
,
raw_report
,
usbhid
->
urbout
->
transfer_buffer_length
);
kfree
(
raw_report
);
dbg_hid
(
"submitting out urb
\n
"
);
dbg_hid
(
"submitting out urb
\n
"
);
if
(
usb_submit_urb
(
usbhid
->
urbout
,
GFP_ATOMIC
))
{
err_hid
(
"usb_submit_urb(out) failed"
);
return
-
1
;
if
(
usb_submit_urb
(
usbhid
->
urbout
,
GFP_ATOMIC
))
{
err_hid
(
"usb_submit_urb(out) failed"
);
return
-
1
;
}
}
else
{
/*
* queue work to wake up the device.
* as the work queue is freezeable, this is safe
* with respect to STD and STR
*/
queue_work
(
resumption_waker
,
&
usbhid
->
restart_work
);
}
return
0
;
...
...
@@ -266,41 +343,50 @@ static int hid_submit_ctrl(struct hid_device *hid)
raw_report
=
usbhid
->
ctrl
[
usbhid
->
ctrltail
].
raw_report
;
dir
=
usbhid
->
ctrl
[
usbhid
->
ctrltail
].
dir
;
len
=
((
report
->
size
-
1
)
>>
3
)
+
1
+
(
report
->
id
>
0
);
if
(
dir
==
USB_DIR_OUT
)
{
usbhid
->
urbctrl
->
pipe
=
usb_sndctrlpipe
(
hid_to_usb_dev
(
hid
),
0
);
usbhid
->
urbctrl
->
transfer_buffer_length
=
len
;
memcpy
(
usbhid
->
ctrlbuf
,
raw_report
,
len
);
kfree
(
raw_report
);
}
else
{
int
maxpacket
,
padlen
;
usbhid
->
urbctrl
->
pipe
=
usb_rcvctrlpipe
(
hid_to_usb_dev
(
hid
),
0
);
maxpacket
=
usb_maxpacket
(
hid_to_usb_dev
(
hid
),
usbhid
->
urbctrl
->
pipe
,
0
);
if
(
maxpacket
>
0
)
{
padlen
=
DIV_ROUND_UP
(
len
,
maxpacket
);
padlen
*=
maxpacket
;
if
(
padlen
>
usbhid
->
bufsize
)
padlen
=
usbhid
->
bufsize
;
}
else
padlen
=
0
;
usbhid
->
urbctrl
->
transfer_buffer_length
=
padlen
;
}
usbhid
->
urbctrl
->
dev
=
hid_to_usb_dev
(
hid
);
if
(
!
test_bit
(
HID_REPORTED_IDLE
,
&
usbhid
->
iofl
))
{
len
=
((
report
->
size
-
1
)
>>
3
)
+
1
+
(
report
->
id
>
0
);
if
(
dir
==
USB_DIR_OUT
)
{
usbhid
->
urbctrl
->
pipe
=
usb_sndctrlpipe
(
hid_to_usb_dev
(
hid
),
0
);
usbhid
->
urbctrl
->
transfer_buffer_length
=
len
;
memcpy
(
usbhid
->
ctrlbuf
,
raw_report
,
len
);
kfree
(
raw_report
);
}
else
{
int
maxpacket
,
padlen
;
usbhid
->
urbctrl
->
pipe
=
usb_rcvctrlpipe
(
hid_to_usb_dev
(
hid
),
0
);
maxpacket
=
usb_maxpacket
(
hid_to_usb_dev
(
hid
),
usbhid
->
urbctrl
->
pipe
,
0
);
if
(
maxpacket
>
0
)
{
padlen
=
DIV_ROUND_UP
(
len
,
maxpacket
);
padlen
*=
maxpacket
;
if
(
padlen
>
usbhid
->
bufsize
)
padlen
=
usbhid
->
bufsize
;
}
else
padlen
=
0
;
usbhid
->
urbctrl
->
transfer_buffer_length
=
padlen
;
}
usbhid
->
urbctrl
->
dev
=
hid_to_usb_dev
(
hid
);
usbhid
->
cr
->
bRequestType
=
USB_TYPE_CLASS
|
USB_RECIP_INTERFACE
|
dir
;
usbhid
->
cr
->
bRequest
=
(
dir
==
USB_DIR_OUT
)
?
HID_REQ_SET_REPORT
:
HID_REQ_GET_REPORT
;
usbhid
->
cr
->
wValue
=
cpu_to_le16
(((
report
->
type
+
1
)
<<
8
)
|
report
->
id
);
usbhid
->
cr
->
wIndex
=
cpu_to_le16
(
usbhid
->
ifnum
);
usbhid
->
cr
->
wLength
=
cpu_to_le16
(
len
);
usbhid
->
cr
->
bRequestType
=
USB_TYPE_CLASS
|
USB_RECIP_INTERFACE
|
dir
;
usbhid
->
cr
->
bRequest
=
(
dir
==
USB_DIR_OUT
)
?
HID_REQ_SET_REPORT
:
HID_REQ_GET_REPORT
;
usbhid
->
cr
->
wValue
=
cpu_to_le16
(((
report
->
type
+
1
)
<<
8
)
|
report
->
id
);
usbhid
->
cr
->
wIndex
=
cpu_to_le16
(
usbhid
->
ifnum
);
usbhid
->
cr
->
wLength
=
cpu_to_le16
(
len
);
dbg_hid
(
"submitting ctrl urb: %s wValue=0x%04x wIndex=0x%04x wLength=%u
\n
"
,
usbhid
->
cr
->
bRequest
==
HID_REQ_SET_REPORT
?
"Set_Report"
:
"Get_Report"
,
usbhid
->
cr
->
wValue
,
usbhid
->
cr
->
wIndex
,
usbhid
->
cr
->
wLength
);
dbg_hid
(
"submitting ctrl urb: %s wValue=0x%04x wIndex=0x%04x wLength=%u
\n
"
,
usbhid
->
cr
->
bRequest
==
HID_REQ_SET_REPORT
?
"Set_Report"
:
"Get_Report"
,
usbhid
->
cr
->
wValue
,
usbhid
->
cr
->
wIndex
,
usbhid
->
cr
->
wLength
);
if
(
usb_submit_urb
(
usbhid
->
urbctrl
,
GFP_ATOMIC
))
{
err_hid
(
"usb_submit_urb(ctrl) failed"
);
return
-
1
;
if
(
usb_submit_urb
(
usbhid
->
urbctrl
,
GFP_ATOMIC
))
{
err_hid
(
"usb_submit_urb(ctrl) failed"
);
return
-
1
;
}
}
else
{
/*
* queue work to wake up the device.
* as the work queue is freezeable, this is safe
* with respect to STD and STR
*/
queue_work
(
resumption_waker
,
&
usbhid
->
restart_work
);
}
return
0
;
...
...
@@ -332,7 +418,7 @@ static void hid_irq_out(struct urb *urb)
"received
\n
"
,
urb
->
status
);
}
spin_lock_irqsave
(
&
usbhid
->
out
lock
,
flags
);
spin_lock_irqsave
(
&
usbhid
->
lock
,
flags
);
if
(
unplug
)
usbhid
->
outtail
=
usbhid
->
outhead
;
...
...
@@ -344,12 +430,12 @@ static void hid_irq_out(struct urb *urb)
clear_bit
(
HID_OUT_RUNNING
,
&
usbhid
->
iofl
);
wake_up
(
&
usbhid
->
wait
);
}
spin_unlock_irqrestore
(
&
usbhid
->
out
lock
,
flags
);
spin_unlock_irqrestore
(
&
usbhid
->
lock
,
flags
);
return
;
}
clear_bit
(
HID_OUT_RUNNING
,
&
usbhid
->
iofl
);
spin_unlock_irqrestore
(
&
usbhid
->
out
lock
,
flags
);
spin_unlock_irqrestore
(
&
usbhid
->
lock
,
flags
);
wake_up
(
&
usbhid
->
wait
);
}
...
...
@@ -361,12 +447,11 @@ static void hid_ctrl(struct urb *urb)
{
struct
hid_device
*
hid
=
urb
->
context
;
struct
usbhid_device
*
usbhid
=
hid
->
driver_data
;
unsigned
long
flags
;
int
unplug
=
0
;
int
unplug
=
0
,
status
=
urb
->
status
;
spin_lock
_irqsave
(
&
usbhid
->
ctrllock
,
flags
);
spin_lock
(
&
usbhid
->
lock
);
switch
(
urb
->
status
)
{
switch
(
status
)
{
case
0
:
/* success */
if
(
usbhid
->
ctrl
[
usbhid
->
ctrltail
].
dir
==
USB_DIR_IN
)
hid_input_report
(
urb
->
context
,
...
...
@@ -383,7 +468,7 @@ static void hid_ctrl(struct urb *urb)
break
;
default:
/* error */
dev_warn
(
&
urb
->
dev
->
dev
,
"ctrl urb status %d "
"received
\n
"
,
urb
->
status
);
"received
\n
"
,
status
);
}
if
(
unplug
)
...
...
@@ -396,19 +481,18 @@ static void hid_ctrl(struct urb *urb)
clear_bit
(
HID_CTRL_RUNNING
,
&
usbhid
->
iofl
);
wake_up
(
&
usbhid
->
wait
);
}
spin_unlock
_irqrestore
(
&
usbhid
->
ctrllock
,
flags
);
spin_unlock
(
&
usbhid
->
lock
);
return
;
}
clear_bit
(
HID_CTRL_RUNNING
,
&
usbhid
->
iofl
);
spin_unlock
_irqrestore
(
&
usbhid
->
ctrllock
,
flags
);
spin_unlock
(
&
usbhid
->
lock
);
wake_up
(
&
usbhid
->
wait
);
}
void
usbhid_submit_report
(
struct
hid_device
*
hid
,
struct
hid_report
*
report
,
unsigned
char
dir
)
void
__
usbhid_submit_report
(
struct
hid_device
*
hid
,
struct
hid_report
*
report
,
unsigned
char
dir
)
{
int
head
;
unsigned
long
flags
;
struct
usbhid_device
*
usbhid
=
hid
->
driver_data
;
int
len
=
((
report
->
size
-
1
)
>>
3
)
+
1
+
(
report
->
id
>
0
);
...
...
@@ -416,18 +500,13 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns
return
;
if
(
usbhid
->
urbout
&&
dir
==
USB_DIR_OUT
&&
report
->
type
==
HID_OUTPUT_REPORT
)
{
spin_lock_irqsave
(
&
usbhid
->
outlock
,
flags
);
if
((
head
=
(
usbhid
->
outhead
+
1
)
&
(
HID_OUTPUT_FIFO_SIZE
-
1
))
==
usbhid
->
outtail
)
{
spin_unlock_irqrestore
(
&
usbhid
->
outlock
,
flags
);
dev_warn
(
&
hid
->
dev
,
"output queue full
\n
"
);
return
;
}
usbhid
->
out
[
usbhid
->
outhead
].
raw_report
=
kmalloc
(
len
,
GFP_ATOMIC
);
if
(
!
usbhid
->
out
[
usbhid
->
outhead
].
raw_report
)
{
spin_unlock_irqrestore
(
&
usbhid
->
outlock
,
flags
);
dev_warn
(
&
hid
->
dev
,
"output queueing failed
\n
"
);
return
;
}
...
...
@@ -438,15 +517,10 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns
if
(
!
test_and_set_bit
(
HID_OUT_RUNNING
,
&
usbhid
->
iofl
))
if
(
hid_submit_out
(
hid
))
clear_bit
(
HID_OUT_RUNNING
,
&
usbhid
->
iofl
);
spin_unlock_irqrestore
(
&
usbhid
->
outlock
,
flags
);
return
;
}
spin_lock_irqsave
(
&
usbhid
->
ctrllock
,
flags
);
if
((
head
=
(
usbhid
->
ctrlhead
+
1
)
&
(
HID_CONTROL_FIFO_SIZE
-
1
))
==
usbhid
->
ctrltail
)
{
spin_unlock_irqrestore
(
&
usbhid
->
ctrllock
,
flags
);
dev_warn
(
&
hid
->
dev
,
"control queue full
\n
"
);
return
;
}
...
...
@@ -454,7 +528,6 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns
if
(
dir
==
USB_DIR_OUT
)
{
usbhid
->
ctrl
[
usbhid
->
ctrlhead
].
raw_report
=
kmalloc
(
len
,
GFP_ATOMIC
);
if
(
!
usbhid
->
ctrl
[
usbhid
->
ctrlhead
].
raw_report
)
{
spin_unlock_irqrestore
(
&
usbhid
->
ctrllock
,
flags
);
dev_warn
(
&
hid
->
dev
,
"control queueing failed
\n
"
);
return
;
}
...
...
@@ -467,15 +540,25 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns
if
(
!
test_and_set_bit
(
HID_CTRL_RUNNING
,
&
usbhid
->
iofl
))
if
(
hid_submit_ctrl
(
hid
))
clear_bit
(
HID_CTRL_RUNNING
,
&
usbhid
->
iofl
);
}
void
usbhid_submit_report
(
struct
hid_device
*
hid
,
struct
hid_report
*
report
,
unsigned
char
dir
)
{
struct
usbhid_device
*
usbhid
=
hid
->
driver_data
;
unsigned
long
flags
;
spin_unlock_irqrestore
(
&
usbhid
->
ctrllock
,
flags
);
spin_lock_irqsave
(
&
usbhid
->
lock
,
flags
);
__usbhid_submit_report
(
hid
,
report
,
dir
);
spin_unlock_irqrestore
(
&
usbhid
->
lock
,
flags
);
}
EXPORT_SYMBOL_GPL
(
usbhid_submit_report
);
static
int
usb_hidinput_input_event
(
struct
input_dev
*
dev
,
unsigned
int
type
,
unsigned
int
code
,
int
value
)
{
struct
hid_device
*
hid
=
input_get_drvdata
(
dev
);
struct
usbhid_device
*
usbhid
=
hid
->
driver_data
;
struct
hid_field
*
field
;
unsigned
long
flags
;
int
offset
;
if
(
type
==
EV_FF
)
...
...
@@ -490,6 +573,15 @@ static int usb_hidinput_input_event(struct input_dev *dev, unsigned int type, un
}
hid_set_field
(
field
,
offset
,
value
);
if
(
value
)
{
spin_lock_irqsave
(
&
usbhid
->
lock
,
flags
);
usbhid
->
ledcount
++
;
spin_unlock_irqrestore
(
&
usbhid
->
lock
,
flags
);
}
else
{
spin_lock_irqsave
(
&
usbhid
->
lock
,
flags
);
usbhid
->
ledcount
--
;
spin_unlock_irqrestore
(
&
usbhid
->
lock
,
flags
);
}
usbhid_submit_report
(
hid
,
field
->
report
,
USB_DIR_OUT
);
return
0
;
...
...
@@ -538,15 +630,22 @@ int usbhid_open(struct hid_device *hid)
struct
usbhid_device
*
usbhid
=
hid
->
driver_data
;
int
res
;
mutex_lock
(
&
hid_open_mut
);
if
(
!
hid
->
open
++
)
{
res
=
usb_autopm_get_interface
(
usbhid
->
intf
);
/* the device must be awake to reliable request remote wakeup */
if
(
res
<
0
)
{
hid
->
open
--
;
mutex_unlock
(
&
hid_open_mut
);
return
-
EIO
;
}
usbhid
->
intf
->
needs_remote_wakeup
=
1
;
if
(
hid_start_in
(
hid
))
hid_io_error
(
hid
);
usb_autopm_put_interface
(
usbhid
->
intf
);
}
if
(
hid_start_in
(
hid
))
hid_io_error
(
hid
);
mutex_unlock
(
&
hid_open_mut
);
return
0
;
}
...
...
@@ -554,10 +653,22 @@ void usbhid_close(struct hid_device *hid)
{
struct
usbhid_device
*
usbhid
=
hid
->
driver_data
;
mutex_lock
(
&
hid_open_mut
);
/* protecting hid->open to make sure we don't restart
* data acquistion due to a resumption we no longer
* care about
*/
spin_lock_irq
(
&
usbhid
->
lock
);
if
(
!--
hid
->
open
)
{
spin_unlock_irq
(
&
usbhid
->
lock
);
usb_kill_urb
(
usbhid
->
urbin
);
usb_autopm_put_interface
(
usbhid
->
intf
);
flush_scheduled_work
();
usbhid
->
intf
->
needs_remote_wakeup
=
0
;
}
else
{
spin_unlock_irq
(
&
usbhid
->
lock
);
}
mutex_unlock
(
&
hid_open_mut
);
}
/*
...
...
@@ -687,6 +798,25 @@ static int usbhid_output_raw_report(struct hid_device *hid, __u8 *buf, size_t co
return
ret
;
}
static
void
usbhid_restart_queues
(
struct
usbhid_device
*
usbhid
)
{
if
(
usbhid
->
urbout
)
usbhid_restart_out_queue
(
usbhid
);
usbhid_restart_ctrl_queue
(
usbhid
);
}
static
void
__usbhid_restart_queues
(
struct
work_struct
*
work
)
{
struct
usbhid_device
*
usbhid
=
container_of
(
work
,
struct
usbhid_device
,
restart_work
);
int
r
;
r
=
usb_autopm_get_interface
(
usbhid
->
intf
);
if
(
r
<
0
)
return
;
usb_autopm_put_interface
(
usbhid
->
intf
);
}
static
void
hid_free_buffers
(
struct
usb_device
*
dev
,
struct
hid_device
*
hid
)
{
struct
usbhid_device
*
usbhid
=
hid
->
driver_data
;
...
...
@@ -853,11 +983,11 @@ static int usbhid_start(struct hid_device *hid)
init_waitqueue_head
(
&
usbhid
->
wait
);
INIT_WORK
(
&
usbhid
->
reset_work
,
hid_reset
);
INIT_WORK
(
&
usbhid
->
restart_work
,
__usbhid_restart_queues
);
setup_timer
(
&
usbhid
->
io_retry
,
hid_retry_timeout
,
(
unsigned
long
)
hid
);
spin_lock_init
(
&
usbhid
->
inlock
);
spin_lock_init
(
&
usbhid
->
outlock
);
spin_lock_init
(
&
usbhid
->
ctrllock
);
spin_lock_init
(
&
usbhid
->
lock
);
spin_lock_init
(
&
usbhid
->
lock
);
usbhid
->
intf
=
intf
;
usbhid
->
ifnum
=
interface
->
desc
.
bInterfaceNumber
;
...
...
@@ -909,15 +1039,14 @@ static void usbhid_stop(struct hid_device *hid)
return
;
clear_bit
(
HID_STARTED
,
&
usbhid
->
iofl
);
spin_lock_irq
(
&
usbhid
->
in
lock
);
/* Sync with error handler */
spin_lock_irq
(
&
usbhid
->
lock
);
/* Sync with error handler */
set_bit
(
HID_DISCONNECTED
,
&
usbhid
->
iofl
);
spin_unlock_irq
(
&
usbhid
->
in
lock
);
spin_unlock_irq
(
&
usbhid
->
lock
);
usb_kill_urb
(
usbhid
->
urbin
);
usb_kill_urb
(
usbhid
->
urbout
);
usb_kill_urb
(
usbhid
->
urbctrl
);
del_timer_sync
(
&
usbhid
->
io_retry
);
cancel_work_sync
(
&
usbhid
->
reset_work
);
hid_cancel_delayed_stuff
(
usbhid
);
if
(
hid
->
claimed
&
HID_CLAIMED_INPUT
)
hidinput_disconnect
(
hid
);
...
...
@@ -938,12 +1067,28 @@ static void usbhid_stop(struct hid_device *hid)
hid_free_buffers
(
hid_to_usb_dev
(
hid
),
hid
);
}
static
int
usbhid_power
(
struct
hid_device
*
hid
,
int
lvl
)
{
int
r
=
0
;
switch
(
lvl
)
{
case
PM_HINT_FULLON
:
r
=
usbhid_get_power
(
hid
);
break
;
case
PM_HINT_NORMAL
:
usbhid_put_power
(
hid
);
break
;
}
return
r
;
}
static
struct
hid_ll_driver
usb_hid_driver
=
{
.
parse
=
usbhid_parse
,
.
start
=
usbhid_start
,
.
stop
=
usbhid_stop
,
.
open
=
usbhid_open
,
.
close
=
usbhid_close
,
.
power
=
usbhid_power
,
.
hidinput_input_event
=
usb_hidinput_input_event
,
};
...
...
@@ -1052,19 +1197,126 @@ static void hid_disconnect(struct usb_interface *intf)
kfree
(
usbhid
);
}
static
void
hid_cancel_delayed_stuff
(
struct
usbhid_device
*
usbhid
)
{
del_timer_sync
(
&
usbhid
->
io_retry
);
cancel_work_sync
(
&
usbhid
->
restart_work
);
cancel_work_sync
(
&
usbhid
->
reset_work
);
}
static
void
hid_cease_io
(
struct
usbhid_device
*
usbhid
)
{
del_timer
(
&
usbhid
->
io_retry
);
usb_kill_urb
(
usbhid
->
urbin
);
usb_kill_urb
(
usbhid
->
urbctrl
);
usb_kill_urb
(
usbhid
->
urbout
);
}
/* Treat USB reset pretty much the same as suspend/resume */
static
int
hid_pre_reset
(
struct
usb_interface
*
intf
)
{
struct
hid_device
*
hid
=
usb_get_intfdata
(
intf
);
struct
usbhid_device
*
usbhid
=
hid
->
driver_data
;
spin_lock_irq
(
&
usbhid
->
lock
);
set_bit
(
HID_RESET_PENDING
,
&
usbhid
->
iofl
);
spin_unlock_irq
(
&
usbhid
->
lock
);
cancel_work_sync
(
&
usbhid
->
restart_work
);
hid_cease_io
(
usbhid
);
return
0
;
}
/* Same routine used for post_reset and reset_resume */
static
int
hid_post_reset
(
struct
usb_interface
*
intf
)
{
struct
usb_device
*
dev
=
interface_to_usbdev
(
intf
);
struct
hid_device
*
hid
=
usb_get_intfdata
(
intf
);
struct
usbhid_device
*
usbhid
=
hid
->
driver_data
;
int
status
;
spin_lock_irq
(
&
usbhid
->
lock
);
clear_bit
(
HID_RESET_PENDING
,
&
usbhid
->
iofl
);
spin_unlock_irq
(
&
usbhid
->
lock
);
hid_set_idle
(
dev
,
intf
->
cur_altsetting
->
desc
.
bInterfaceNumber
,
0
,
0
);
/* FIXME: Any more reinitialization needed? */
status
=
hid_start_in
(
hid
);
if
(
status
<
0
)
hid_io_error
(
hid
);
usbhid_restart_queues
(
usbhid
);
return
0
;
}
int
usbhid_get_power
(
struct
hid_device
*
hid
)
{
struct
usbhid_device
*
usbhid
=
hid
->
driver_data
;
return
usb_autopm_get_interface
(
usbhid
->
intf
);
}
void
usbhid_put_power
(
struct
hid_device
*
hid
)
{
struct
usbhid_device
*
usbhid
=
hid
->
driver_data
;
usb_autopm_put_interface
(
usbhid
->
intf
);
}
#ifdef CONFIG_PM
static
int
hid_suspend
(
struct
usb_interface
*
intf
,
pm_message_t
message
)
{
struct
hid_device
*
hid
=
usb_get_intfdata
(
intf
);
struct
hid_device
*
hid
=
usb_get_intfdata
(
intf
);
struct
usbhid_device
*
usbhid
=
hid
->
driver_data
;
struct
usb_device
*
udev
=
interface_to_usbdev
(
intf
);
int
status
;
if
(
!
test_bit
(
HID_STARTED
,
&
usbhid
->
iofl
))
return
0
;
if
(
udev
->
auto_pm
)
{
spin_lock_irq
(
&
usbhid
->
lock
);
/* Sync with error handler */
if
(
!
test_bit
(
HID_RESET_PENDING
,
&
usbhid
->
iofl
)
&&
!
test_bit
(
HID_CLEAR_HALT
,
&
usbhid
->
iofl
)
&&
!
test_bit
(
HID_OUT_RUNNING
,
&
usbhid
->
iofl
)
&&
!
test_bit
(
HID_CTRL_RUNNING
,
&
usbhid
->
iofl
)
&&
!
test_bit
(
HID_KEYS_PRESSED
,
&
usbhid
->
iofl
)
&&
(
!
usbhid
->
ledcount
||
ignoreled
))
{
set_bit
(
HID_REPORTED_IDLE
,
&
usbhid
->
iofl
);
spin_unlock_irq
(
&
usbhid
->
lock
);
}
else
{
usbhid_mark_busy
(
usbhid
);
spin_unlock_irq
(
&
usbhid
->
lock
);
return
-
EBUSY
;
}
spin_lock_irq
(
&
usbhid
->
inlock
);
/* Sync with error handler */
set_bit
(
HID_SUSPENDED
,
&
usbhid
->
iofl
);
spin_unlock_irq
(
&
usbhid
->
inlock
);
del_timer_sync
(
&
usbhid
->
io_retry
);
usb_kill_urb
(
usbhid
->
urbin
);
}
else
{
spin_lock_irq
(
&
usbhid
->
lock
);
set_bit
(
HID_REPORTED_IDLE
,
&
usbhid
->
iofl
);
spin_unlock_irq
(
&
usbhid
->
lock
);
if
(
usbhid_wait_io
(
hid
)
<
0
)
return
-
EIO
;
}
if
(
!
ignoreled
&&
udev
->
auto_pm
)
{
spin_lock_irq
(
&
usbhid
->
lock
);
if
(
test_bit
(
HID_LED_ON
,
&
usbhid
->
iofl
))
{
spin_unlock_irq
(
&
usbhid
->
lock
);
usbhid_mark_busy
(
usbhid
);
return
-
EBUSY
;
}
spin_unlock_irq
(
&
usbhid
->
lock
);
}
hid_cancel_delayed_stuff
(
usbhid
);
hid_cease_io
(
usbhid
);
if
(
udev
->
auto_pm
&&
test_bit
(
HID_KEYS_PRESSED
,
&
usbhid
->
iofl
))
{
/* lost race against keypresses */
status
=
hid_start_in
(
hid
);
if
(
status
<
0
)
hid_io_error
(
hid
);
usbhid_mark_busy
(
usbhid
);
return
-
EBUSY
;
}
dev_dbg
(
&
intf
->
dev
,
"suspend
\n
"
);
return
0
;
}
...
...
@@ -1078,32 +1330,33 @@ static int hid_resume(struct usb_interface *intf)
if
(
!
test_bit
(
HID_STARTED
,
&
usbhid
->
iofl
))
return
0
;
clear_bit
(
HID_SUSPENDED
,
&
usbhid
->
iofl
);
clear_bit
(
HID_REPORTED_IDLE
,
&
usbhid
->
iofl
);
usbhid_mark_busy
(
usbhid
);
if
(
test_bit
(
HID_CLEAR_HALT
,
&
usbhid
->
iofl
)
||
test_bit
(
HID_RESET_PENDING
,
&
usbhid
->
iofl
))
schedule_work
(
&
usbhid
->
reset_work
);
usbhid
->
retry_delay
=
0
;
status
=
hid_start_in
(
hid
);
dev_dbg
(
&
intf
->
dev
,
"resume status %d
\n
"
,
status
);
return
status
;
}
if
(
status
<
0
)
hid_io_error
(
hid
)
;
usbhid_restart_queues
(
usbhid
);
/* Treat USB reset pretty much the same as suspend/resume */
static
int
hid_pre_reset
(
struct
usb_interface
*
intf
)
{
/* FIXME: What if the interface is already suspended? */
hid_suspend
(
intf
,
PMSG_ON
);
dev_dbg
(
&
intf
->
dev
,
"resume status %d
\n
"
,
status
);
return
0
;
}
/* Same routine used for post_reset and reset_resume */
static
int
hid_post_reset
(
struct
usb_interface
*
intf
)
static
int
hid_reset_resume
(
struct
usb_interface
*
intf
)
{
struct
usb_device
*
dev
=
interface_to_usbdev
(
intf
);
hid_set_idle
(
dev
,
intf
->
cur_altsetting
->
desc
.
bInterfaceNumber
,
0
,
0
);
/* FIXME: Any more reinitialization needed? */
struct
hid_device
*
hid
=
usb_get_intfdata
(
intf
);
struct
usbhid_device
*
usbhid
=
hid
->
driver_data
;
return
hid_resume
(
intf
);
clear_bit
(
HID_REPORTED_IDLE
,
&
usbhid
->
iofl
);
return
hid_post_reset
(
intf
);
}
#endif
/* CONFIG_PM */
static
struct
usb_device_id
hid_usb_ids
[]
=
{
{
.
match_flags
=
USB_DEVICE_ID_MATCH_INT_CLASS
,
.
bInterfaceClass
=
USB_INTERFACE_CLASS_HID
},
...
...
@@ -1116,9 +1369,11 @@ static struct usb_driver hid_driver = {
.
name
=
"usbhid"
,
.
probe
=
hid_probe
,
.
disconnect
=
hid_disconnect
,
#ifdef CONFIG_PM
.
suspend
=
hid_suspend
,
.
resume
=
hid_resume
,
.
reset_resume
=
hid_post_reset
,
.
reset_resume
=
hid_reset_resume
,
#endif
.
pre_reset
=
hid_pre_reset
,
.
post_reset
=
hid_post_reset
,
.
id_table
=
hid_usb_ids
,
...
...
@@ -1137,7 +1392,11 @@ static struct hid_driver hid_usb_driver = {
static
int
__init
hid_init
(
void
)
{
int
retval
;
int
retval
=
-
ENOMEM
;
resumption_waker
=
create_freezeable_workqueue
(
"usbhid_resumer"
);
if
(
!
resumption_waker
)
goto
no_queue
;
retval
=
hid_register_driver
(
&
hid_usb_driver
);
if
(
retval
)
goto
hid_register_fail
;
...
...
@@ -1161,6 +1420,8 @@ static int __init hid_init(void)
usbhid_quirks_init_fail:
hid_unregister_driver
(
&
hid_usb_driver
);
hid_register_fail:
destroy_workqueue
(
resumption_waker
);
no_queue:
return
retval
;
}
...
...
@@ -1170,6 +1431,7 @@ static void __exit hid_exit(void)
hiddev_exit
();
usbhid_quirks_exit
();
hid_unregister_driver
(
&
hid_usb_driver
);
destroy_workqueue
(
resumption_waker
);
}
module_init
(
hid_init
);
...
...
drivers/hid/usbhid/hiddev.c
View file @
621de593
...
...
@@ -246,10 +246,12 @@ static int hiddev_release(struct inode * inode, struct file * file)
spin_unlock_irqrestore
(
&
list
->
hiddev
->
list_lock
,
flags
);
if
(
!--
list
->
hiddev
->
open
)
{
if
(
list
->
hiddev
->
exist
)
if
(
list
->
hiddev
->
exist
)
{
usbhid_close
(
list
->
hiddev
->
hid
);
else
usbhid_put_power
(
list
->
hiddev
->
hid
);
}
else
{
kfree
(
list
->
hiddev
);
}
}
kfree
(
list
);
...
...
@@ -300,6 +302,17 @@ static int hiddev_open(struct inode *inode, struct file *file)
list_add_tail
(
&
list
->
node
,
&
hiddev_table
[
i
]
->
list
);
spin_unlock_irq
(
&
list
->
hiddev
->
list_lock
);
if
(
!
list
->
hiddev
->
open
++
)
if
(
list
->
hiddev
->
exist
)
{
struct
hid_device
*
hid
=
hiddev_table
[
i
]
->
hid
;
res
=
usbhid_get_power
(
hid
);
if
(
res
<
0
)
{
res
=
-
EIO
;
goto
bail
;
}
usbhid_open
(
hid
);
}
return
0
;
bail:
file
->
private_data
=
NULL
;
...
...
drivers/hid/usbhid/usbhid.h
View file @
621de593
...
...
@@ -38,7 +38,10 @@ int usbhid_wait_io(struct hid_device* hid);
void
usbhid_close
(
struct
hid_device
*
hid
);
int
usbhid_open
(
struct
hid_device
*
hid
);
void
usbhid_init_reports
(
struct
hid_device
*
hid
);
void
usbhid_submit_report
(
struct
hid_device
*
hid
,
struct
hid_report
*
report
,
unsigned
char
dir
);
void
usbhid_submit_report
(
struct
hid_device
*
hid
,
struct
hid_report
*
report
,
unsigned
char
dir
);
int
usbhid_get_power
(
struct
hid_device
*
hid
);
void
usbhid_put_power
(
struct
hid_device
*
hid
);
/* iofl flags */
#define HID_CTRL_RUNNING 1
...
...
@@ -49,6 +52,9 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns
#define HID_CLEAR_HALT 6
#define HID_DISCONNECTED 7
#define HID_STARTED 8
#define HID_REPORTED_IDLE 9
#define HID_KEYS_PRESSED 10
#define HID_LED_ON 11
/*
* USB-specific HID struct, to be pointed to
...
...
@@ -66,7 +72,6 @@ struct usbhid_device {
struct
urb
*
urbin
;
/* Input URB */
char
*
inbuf
;
/* Input buffer */
dma_addr_t
inbuf_dma
;
/* Input buffer dma */
spinlock_t
inlock
;
/* Input fifo spinlock */
struct
urb
*
urbctrl
;
/* Control URB */
struct
usb_ctrlrequest
*
cr
;
/* Control request struct */
...
...
@@ -75,21 +80,22 @@ struct usbhid_device {
unsigned
char
ctrlhead
,
ctrltail
;
/* Control fifo head & tail */
char
*
ctrlbuf
;
/* Control buffer */
dma_addr_t
ctrlbuf_dma
;
/* Control buffer dma */
spinlock_t
ctrllock
;
/* Control fifo spinlock */
struct
urb
*
urbout
;
/* Output URB */
struct
hid_output_fifo
out
[
HID_CONTROL_FIFO_SIZE
];
/* Output pipe fifo */
unsigned
char
outhead
,
outtail
;
/* Output pipe fifo head & tail */
char
*
outbuf
;
/* Output buffer */
dma_addr_t
outbuf_dma
;
/* Output buffer dma */
spinlock_t
outlock
;
/* Output fifo spinlock */
spinlock_t
lock
;
/* fifo spinlock */
unsigned
long
iofl
;
/* I/O flags (CTRL_RUNNING, OUT_RUNNING) */
struct
timer_list
io_retry
;
/* Retry timer */
unsigned
long
stop_retry
;
/* Time to give up, in jiffies */
unsigned
int
retry_delay
;
/* Delay length in ms */
struct
work_struct
reset_work
;
/* Task context for resets */
struct
work_struct
restart_work
;
/* waking up for output to be done in a task */
wait_queue_head_t
wait
;
/* For sleeping */
int
ledcount
;
/* counting the number of active leds */
};
#define hid_to_usb_dev(hid_dev) \
...
...
include/linux/hid.h
View file @
621de593
...
...
@@ -604,12 +604,17 @@ struct hid_ll_driver {
int
(
*
open
)(
struct
hid_device
*
hdev
);
void
(
*
close
)(
struct
hid_device
*
hdev
);
int
(
*
power
)(
struct
hid_device
*
hdev
,
int
level
);
int
(
*
hidinput_input_event
)
(
struct
input_dev
*
idev
,
unsigned
int
type
,
unsigned
int
code
,
int
value
);
int
(
*
parse
)(
struct
hid_device
*
hdev
);
};
#define PM_HINT_FULLON 1<<5
#define PM_HINT_NORMAL 1<<1
/* Applications from HID Usage Tables 4/8/99 Version 1.1 */
/* We ignore a few input applications that are not widely used */
#define IS_INPUT_APPLICATION(a) (((a >= 0x00010000) && (a <= 0x00010008)) || (a == 0x00010080) || (a == 0x000c0001) || (a == 0x000d0002))
...
...
@@ -642,6 +647,7 @@ int hidinput_find_field(struct hid_device *hid, unsigned int type, unsigned int
void
hid_output_report
(
struct
hid_report
*
report
,
__u8
*
data
);
struct
hid_device
*
hid_allocate_device
(
void
);
int
hid_parse_report
(
struct
hid_device
*
hid
,
__u8
*
start
,
unsigned
size
);
int
hid_check_keys_pressed
(
struct
hid_device
*
hid
);
int
hid_connect
(
struct
hid_device
*
hid
,
unsigned
int
connect_mask
);
/**
...
...
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