Merge branch 'android-4.4' of https://android.googlesource.com/kernel/common
* android-4.4: (475 commits) android: base-cfg: Add CONFIG_IP_MULTICAST android: recommended.cfg: enable taskstats ANDROID: android: base-cfg: disable CONFIG_SYSVIPC android: configs: base: enable configfs gadget functions android: add CONFIG_DEBUG_RODATA to recommended config android: configs: remove CONFIG_BATTERY_ANDROID=y android: configs: base: enable IPV6 android: configs: Enable SELinux and its dependencies. android: base-cfg: disable ALARM_DEV android: base-cfg: turn off /dev/mem and /dev/kmem android: base-cfg: enable ARMV8_DEPRECATED and subfeatures android: base-cfg: enforce the needed XFRM_MODE_TUNNEL (for VPN) android: base-cfg: disable LOGGER android: base-cfg: enable DM_VERITY (used for secureboot) android: configs: add systrace support to recommended configs android: configs: update 3.10 options android: configs: Add CONFIG_NETFILTER_XT_TARGET_IDLETIMER android: configs: add IPV6 ROUTE INFO android: configs: add TIMER_STATS back, helps with sysrq t. android: configs: Add HIDRAW to recommended set ...
This commit is contained in:
commit
02bbd06e48
349 changed files with 30285 additions and 550 deletions
71
Documentation/ABI/testing/sysfs-class-dual-role-usb
Normal file
71
Documentation/ABI/testing/sysfs-class-dual-role-usb
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
What: /sys/class/dual_role_usb/.../
|
||||
Date: June 2015
|
||||
Contact: Badhri Jagan Sridharan<badhri@google.com>
|
||||
Description:
|
||||
Provide a generic interface to monitor and change
|
||||
the state of dual role usb ports. The name here
|
||||
refers to the name mentioned in the
|
||||
dual_role_phy_desc that is passed while registering
|
||||
the dual_role_phy_intstance through
|
||||
devm_dual_role_instance_register.
|
||||
|
||||
What: /sys/class/dual_role_usb/.../supported_modes
|
||||
Date: June 2015
|
||||
Contact: Badhri Jagan Sridharan<badhri@google.com>
|
||||
Description:
|
||||
This is a static node, once initialized this
|
||||
is not expected to change during runtime. "dfp"
|
||||
refers to "downstream facing port" i.e. port can
|
||||
only act as host. "ufp" refers to "upstream
|
||||
facing port" i.e. port can only act as device.
|
||||
"dfp ufp" refers to "dual role port" i.e. the port
|
||||
can either be a host port or a device port.
|
||||
|
||||
What: /sys/class/dual_role_usb/.../mode
|
||||
Date: June 2015
|
||||
Contact: Badhri Jagan Sridharan<badhri@google.com>
|
||||
Description:
|
||||
The mode node refers to the current mode in which the
|
||||
port is operating. "dfp" for host ports. "ufp" for device
|
||||
ports and "none" when cable is not connected.
|
||||
|
||||
On devices where the USB mode is software-controllable,
|
||||
userspace can change the mode by writing "dfp" or "ufp".
|
||||
On devices where the USB mode is fixed in hardware,
|
||||
this attribute is read-only.
|
||||
|
||||
What: /sys/class/dual_role_usb/.../power_role
|
||||
Date: June 2015
|
||||
Contact: Badhri Jagan Sridharan<badhri@google.com>
|
||||
Description:
|
||||
The power_role node mentions whether the port
|
||||
is "sink"ing or "source"ing power. "none" if
|
||||
they are not connected.
|
||||
|
||||
On devices implementing USB Power Delivery,
|
||||
userspace can control the power role by writing "sink" or
|
||||
"source". On devices without USB-PD, this attribute is
|
||||
read-only.
|
||||
|
||||
What: /sys/class/dual_role_usb/.../data_role
|
||||
Date: June 2015
|
||||
Contact: Badhri Jagan Sridharan<badhri@google.com>
|
||||
Description:
|
||||
The data_role node mentions whether the port
|
||||
is acting as "host" or "device" for USB data connection.
|
||||
"none" if there is no active data link.
|
||||
|
||||
On devices implementing USB Power Delivery, userspace
|
||||
can control the data role by writing "host" or "device".
|
||||
On devices without USB-PD, this attribute is read-only
|
||||
|
||||
What: /sys/class/dual_role_usb/.../powers_vconn
|
||||
Date: June 2015
|
||||
Contact: Badhri Jagan Sridharan<badhri@google.com>
|
||||
Description:
|
||||
The powers_vconn node mentions whether the port
|
||||
is supplying power for VCONN pin.
|
||||
|
||||
On devices with software control of VCONN,
|
||||
userspace can disable the power supply to VCONN by writing "n",
|
||||
or enable the power supply by writing "y".
|
||||
16
Documentation/ABI/testing/sysfs-kernel-wakeup_reasons
Normal file
16
Documentation/ABI/testing/sysfs-kernel-wakeup_reasons
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
What: /sys/kernel/wakeup_reasons/last_resume_reason
|
||||
Date: February 2014
|
||||
Contact: Ruchi Kandoi <kandoiruchi@google.com>
|
||||
Description:
|
||||
The /sys/kernel/wakeup_reasons/last_resume_reason is
|
||||
used to report wakeup reasons after system exited suspend.
|
||||
|
||||
What: /sys/kernel/wakeup_reasons/last_suspend_time
|
||||
Date: March 2015
|
||||
Contact: jinqian <jinqian@google.com>
|
||||
Description:
|
||||
The /sys/kernel/wakeup_reasons/last_suspend_time is
|
||||
used to report time spent in last suspend cycle. It contains
|
||||
two numbers (in seconds) separated by space. First number is
|
||||
the time spent in suspend and resume processes. Second number
|
||||
is the time spent in sleep state.
|
||||
121
Documentation/android.txt
Normal file
121
Documentation/android.txt
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
=============
|
||||
A N D R O I D
|
||||
=============
|
||||
|
||||
Copyright (C) 2009 Google, Inc.
|
||||
Written by Mike Chan <mike@android.com>
|
||||
|
||||
CONTENTS:
|
||||
---------
|
||||
|
||||
1. Android
|
||||
1.1 Required enabled config options
|
||||
1.2 Required disabled config options
|
||||
1.3 Recommended enabled config options
|
||||
2. Contact
|
||||
|
||||
|
||||
1. Android
|
||||
==========
|
||||
|
||||
Android (www.android.com) is an open source operating system for mobile devices.
|
||||
This document describes configurations needed to run the Android framework on
|
||||
top of the Linux kernel.
|
||||
|
||||
To see a working defconfig look at msm_defconfig or goldfish_defconfig
|
||||
which can be found at http://android.git.kernel.org in kernel/common.git
|
||||
and kernel/msm.git
|
||||
|
||||
|
||||
1.1 Required enabled config options
|
||||
-----------------------------------
|
||||
After building a standard defconfig, ensure that these options are enabled in
|
||||
your .config or defconfig if they are not already. Based off the msm_defconfig.
|
||||
You should keep the rest of the default options enabled in the defconfig
|
||||
unless you know what you are doing.
|
||||
|
||||
ANDROID_PARANOID_NETWORK
|
||||
ASHMEM
|
||||
CONFIG_FB_MODE_HELPERS
|
||||
CONFIG_FONT_8x16
|
||||
CONFIG_FONT_8x8
|
||||
CONFIG_YAFFS_SHORT_NAMES_IN_RAM
|
||||
DAB
|
||||
EARLYSUSPEND
|
||||
FB
|
||||
FB_CFB_COPYAREA
|
||||
FB_CFB_FILLRECT
|
||||
FB_CFB_IMAGEBLIT
|
||||
FB_DEFERRED_IO
|
||||
FB_TILEBLITTING
|
||||
HIGH_RES_TIMERS
|
||||
INOTIFY
|
||||
INOTIFY_USER
|
||||
INPUT_EVDEV
|
||||
INPUT_GPIO
|
||||
INPUT_MISC
|
||||
LEDS_CLASS
|
||||
LEDS_GPIO
|
||||
LOCK_KERNEL
|
||||
LkOGGER
|
||||
LOW_MEMORY_KILLER
|
||||
MISC_DEVICES
|
||||
NEW_LEDS
|
||||
NO_HZ
|
||||
POWER_SUPPLY
|
||||
PREEMPT
|
||||
RAMFS
|
||||
RTC_CLASS
|
||||
RTC_LIB
|
||||
SWITCH
|
||||
SWITCH_GPIO
|
||||
TMPFS
|
||||
UID_STAT
|
||||
UID16
|
||||
USB_FUNCTION
|
||||
USB_FUNCTION_ADB
|
||||
USER_WAKELOCK
|
||||
VIDEO_OUTPUT_CONTROL
|
||||
WAKELOCK
|
||||
YAFFS_AUTO_YAFFS2
|
||||
YAFFS_FS
|
||||
YAFFS_YAFFS1
|
||||
YAFFS_YAFFS2
|
||||
|
||||
|
||||
1.2 Required disabled config options
|
||||
------------------------------------
|
||||
CONFIG_YAFFS_DISABLE_LAZY_LOAD
|
||||
DNOTIFY
|
||||
|
||||
|
||||
1.3 Recommended enabled config options
|
||||
------------------------------
|
||||
ANDROID_PMEM
|
||||
PSTORE_CONSOLE
|
||||
PSTORE_RAM
|
||||
SCHEDSTATS
|
||||
DEBUG_PREEMPT
|
||||
DEBUG_MUTEXES
|
||||
DEBUG_SPINLOCK_SLEEP
|
||||
DEBUG_INFO
|
||||
FRAME_POINTER
|
||||
CPU_FREQ
|
||||
CPU_FREQ_TABLE
|
||||
CPU_FREQ_DEFAULT_GOV_ONDEMAND
|
||||
CPU_FREQ_GOV_ONDEMAND
|
||||
CRC_CCITT
|
||||
EMBEDDED
|
||||
INPUT_TOUCHSCREEN
|
||||
I2C
|
||||
I2C_BOARDINFO
|
||||
LOG_BUF_SHIFT=17
|
||||
SERIAL_CORE
|
||||
SERIAL_CORE_CONSOLE
|
||||
|
||||
|
||||
2. Contact
|
||||
==========
|
||||
website: http://android.git.kernel.org
|
||||
|
||||
mailing-lists: android-kernel@googlegroups.com
|
||||
|
|
@ -578,6 +578,15 @@ is completely unused; @cgrp->parent is still valid. (Note - can also
|
|||
be called for a newly-created cgroup if an error occurs after this
|
||||
subsystem's create() method has been called for the new cgroup).
|
||||
|
||||
int allow_attach(struct cgroup *cgrp, struct cgroup_taskset *tset)
|
||||
(cgroup_mutex held by caller)
|
||||
|
||||
Called prior to moving a task into a cgroup; if the subsystem
|
||||
returns an error, this will abort the attach operation. Used
|
||||
to extend the permission checks - if all subsystems in a cgroup
|
||||
return 0, the attach will be allowed to proceed, even if the
|
||||
default permission check (root or same user) fails.
|
||||
|
||||
int can_attach(struct cgroup *cgrp, struct cgroup_taskset *tset)
|
||||
(cgroup_mutex held by caller)
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ Contents:
|
|||
2.3 Userspace
|
||||
2.4 Ondemand
|
||||
2.5 Conservative
|
||||
2.6 Interactive
|
||||
|
||||
3. The Governor Interface in the CPUfreq Core
|
||||
|
||||
|
|
@ -218,6 +219,90 @@ a decision on when to decrease the frequency while running in any
|
|||
speed. Load for frequency increase is still evaluated every
|
||||
sampling rate.
|
||||
|
||||
2.6 Interactive
|
||||
---------------
|
||||
|
||||
The CPUfreq governor "interactive" is designed for latency-sensitive,
|
||||
interactive workloads. This governor sets the CPU speed depending on
|
||||
usage, similar to "ondemand" and "conservative" governors, but with a
|
||||
different set of configurable behaviors.
|
||||
|
||||
The tuneable values for this governor are:
|
||||
|
||||
target_loads: CPU load values used to adjust speed to influence the
|
||||
current CPU load toward that value. In general, the lower the target
|
||||
load, the more often the governor will raise CPU speeds to bring load
|
||||
below the target. The format is a single target load, optionally
|
||||
followed by pairs of CPU speeds and CPU loads to target at or above
|
||||
those speeds. Colons can be used between the speeds and associated
|
||||
target loads for readability. For example:
|
||||
|
||||
85 1000000:90 1700000:99
|
||||
|
||||
targets CPU load 85% below speed 1GHz, 90% at or above 1GHz, until
|
||||
1.7GHz and above, at which load 99% is targeted. If speeds are
|
||||
specified these must appear in ascending order. Higher target load
|
||||
values are typically specified for higher speeds, that is, target load
|
||||
values also usually appear in an ascending order. The default is
|
||||
target load 90% for all speeds.
|
||||
|
||||
min_sample_time: The minimum amount of time to spend at the current
|
||||
frequency before ramping down. Default is 80000 uS.
|
||||
|
||||
hispeed_freq: An intermediate "hi speed" at which to initially ramp
|
||||
when CPU load hits the value specified in go_hispeed_load. If load
|
||||
stays high for the amount of time specified in above_hispeed_delay,
|
||||
then speed may be bumped higher. Default is the maximum speed
|
||||
allowed by the policy at governor initialization time.
|
||||
|
||||
go_hispeed_load: The CPU load at which to ramp to hispeed_freq.
|
||||
Default is 99%.
|
||||
|
||||
above_hispeed_delay: When speed is at or above hispeed_freq, wait for
|
||||
this long before raising speed in response to continued high load.
|
||||
The format is a single delay value, optionally followed by pairs of
|
||||
CPU speeds and the delay to use at or above those speeds. Colons can
|
||||
be used between the speeds and associated delays for readability. For
|
||||
example:
|
||||
|
||||
80000 1300000:200000 1500000:40000
|
||||
|
||||
uses delay 80000 uS until CPU speed 1.3 GHz, at which speed delay
|
||||
200000 uS is used until speed 1.5 GHz, at which speed (and above)
|
||||
delay 40000 uS is used. If speeds are specified these must appear in
|
||||
ascending order. Default is 20000 uS.
|
||||
|
||||
timer_rate: Sample rate for reevaluating CPU load when the CPU is not
|
||||
idle. A deferrable timer is used, such that the CPU will not be woken
|
||||
from idle to service this timer until something else needs to run.
|
||||
(The maximum time to allow deferring this timer when not running at
|
||||
minimum speed is configurable via timer_slack.) Default is 20000 uS.
|
||||
|
||||
timer_slack: Maximum additional time to defer handling the governor
|
||||
sampling timer beyond timer_rate when running at speeds above the
|
||||
minimum. For platforms that consume additional power at idle when
|
||||
CPUs are running at speeds greater than minimum, this places an upper
|
||||
bound on how long the timer will be deferred prior to re-evaluating
|
||||
load and dropping speed. For example, if timer_rate is 20000uS and
|
||||
timer_slack is 10000uS then timers will be deferred for up to 30msec
|
||||
when not at lowest speed. A value of -1 means defer timers
|
||||
indefinitely at all speeds. Default is 80000 uS.
|
||||
|
||||
boost: If non-zero, immediately boost speed of all CPUs to at least
|
||||
hispeed_freq until zero is written to this attribute. If zero, allow
|
||||
CPU speeds to drop below hispeed_freq according to load as usual.
|
||||
Default is zero.
|
||||
|
||||
boostpulse: On each write, immediately boost speed of all CPUs to
|
||||
hispeed_freq for at least the period of time specified by
|
||||
boostpulse_duration, after which speeds are allowed to drop below
|
||||
hispeed_freq according to load as usual.
|
||||
|
||||
boostpulse_duration: Length of time to hold CPU speed at hispeed_freq
|
||||
on a write to boostpulse, before allowing speed to drop according to
|
||||
load as usual. Default is 80000 uS.
|
||||
|
||||
|
||||
3. The Governor Interface in the CPUfreq Core
|
||||
=============================================
|
||||
|
||||
|
|
|
|||
43
Documentation/devicetree/bindings/misc/ramoops.txt
Normal file
43
Documentation/devicetree/bindings/misc/ramoops.txt
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
Ramoops oops/panic logger
|
||||
=========================
|
||||
|
||||
ramoops provides persistent RAM storage for oops and panics, so they can be
|
||||
recovered after a reboot.
|
||||
|
||||
Parts of this storage may be set aside for other persistent log buffers, such
|
||||
as kernel log messages, or for optional ECC error-correction data. The total
|
||||
size of these optional buffers must fit in the reserved region.
|
||||
|
||||
Any remaining space will be used for a circular buffer of oops and panic
|
||||
records. These records have a configurable size, with a size of 0 indicating
|
||||
that they should be disabled.
|
||||
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible: must be "ramoops"
|
||||
|
||||
- memory-region: phandle to a region of memory that is preserved between reboots
|
||||
|
||||
|
||||
Optional properties:
|
||||
|
||||
- ecc-size: enables ECC support and specifies ECC buffer size in bytes
|
||||
(defaults to no ECC)
|
||||
|
||||
- record-size: maximum size in bytes of each dump done on oops/panic
|
||||
(defaults to 0)
|
||||
|
||||
- console-size: size in bytes of log buffer reserved for kernel messages
|
||||
(defaults to 0)
|
||||
|
||||
- ftrace-size: size in bytes of log buffer reserved for function tracing and
|
||||
profiling (defaults to 0)
|
||||
|
||||
- pmsg-size: size in bytes of log buffer reserved for userspace messages
|
||||
(defaults to 0)
|
||||
|
||||
- unbuffered: if present, use unbuffered mappings to map the reserved region
|
||||
(defaults to buffered mappings)
|
||||
|
||||
- no-dump-oops: if present, only dump panics (defaults to panics and oops)
|
||||
|
|
@ -381,6 +381,8 @@ is not associated with a file:
|
|||
[stack:1001] = the stack of the thread with tid 1001
|
||||
[vdso] = the "virtual dynamic shared object",
|
||||
the kernel system call handler
|
||||
[anon:<name>] = an anonymous mapping that has been
|
||||
named by userspace
|
||||
|
||||
or if empty, the mapping is anonymous.
|
||||
|
||||
|
|
@ -435,6 +437,7 @@ KernelPageSize: 4 kB
|
|||
MMUPageSize: 4 kB
|
||||
Locked: 0 kB
|
||||
VmFlags: rd ex mr mw me dw
|
||||
Name: name from userspace
|
||||
|
||||
the first of these lines shows the same information as is displayed for the
|
||||
mapping in /proc/PID/maps. The remaining lines show the size of the mapping
|
||||
|
|
@ -497,6 +500,9 @@ Note that there is no guarantee that every flag and associated mnemonic will
|
|||
be present in all further kernel releases. Things get changed, the flags may
|
||||
be vanished or the reverse -- new added.
|
||||
|
||||
The "Name" field will only be present on a mapping that has been named by
|
||||
userspace, and will show the name passed in by userspace.
|
||||
|
||||
This file is only present if the CONFIG_MMU kernel configuration option is
|
||||
enabled.
|
||||
|
||||
|
|
|
|||
|
|
@ -584,6 +584,16 @@ tcp_fastopen - INTEGER
|
|||
|
||||
See include/net/tcp.h and the code for more details.
|
||||
|
||||
tcp_fwmark_accept - BOOLEAN
|
||||
If set, incoming connections to listening sockets that do not have a
|
||||
socket mark will set the mark of the accepting socket to the fwmark of
|
||||
the incoming SYN packet. This will cause all packets on that connection
|
||||
(starting from the first SYNACK) to be sent with that fwmark. The
|
||||
listening socket's mark is unchanged. Listening sockets that already
|
||||
have a fwmark set via setsockopt(SOL_SOCKET, SO_MARK, ...) are
|
||||
unaffected.
|
||||
Default: 0
|
||||
|
||||
tcp_syn_retries - INTEGER
|
||||
Number of times initial SYNs for an active TCP connection attempt
|
||||
will be retransmitted. Should not be higher than 255. Default value
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ corrupt, but usually it is restorable.
|
|||
|
||||
2. Setting the parameters
|
||||
|
||||
Setting the ramoops parameters can be done in 2 different manners:
|
||||
Setting the ramoops parameters can be done in 3 different manners:
|
||||
1. Use the module parameters (which have the names of the variables described
|
||||
as before).
|
||||
For quick debugging, you can also reserve parts of memory during boot
|
||||
|
|
@ -54,7 +54,9 @@ Setting the ramoops parameters can be done in 2 different manners:
|
|||
kernel to use only the first 128 MB of memory, and place ECC-protected ramoops
|
||||
region at 128 MB boundary:
|
||||
"mem=128M ramoops.mem_address=0x8000000 ramoops.ecc=1"
|
||||
2. Use a platform device and set the platform data. The parameters can then
|
||||
2. Use Device Tree bindings, as described in
|
||||
Documentation/device-tree/bindings/misc/ramoops.txt.
|
||||
3. Use a platform device and set the platform data. The parameters can then
|
||||
be set through that platform data. An example of doing that is:
|
||||
|
||||
#include <linux/pstore_ram.h>
|
||||
|
|
|
|||
75
Documentation/sync.txt
Normal file
75
Documentation/sync.txt
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
Motivation:
|
||||
|
||||
In complicated DMA pipelines such as graphics (multimedia, camera, gpu, display)
|
||||
a consumer of a buffer needs to know when the producer has finished producing
|
||||
it. Likewise the producer needs to know when the consumer is finished with the
|
||||
buffer so it can reuse it. A particular buffer may be consumed by multiple
|
||||
consumers which will retain the buffer for different amounts of time. In
|
||||
addition, a consumer may consume multiple buffers atomically.
|
||||
The sync framework adds an API which allows synchronization between the
|
||||
producers and consumers in a generic way while also allowing platforms which
|
||||
have shared hardware synchronization primitives to exploit them.
|
||||
|
||||
Goals:
|
||||
* provide a generic API for expressing synchronization dependencies
|
||||
* allow drivers to exploit hardware synchronization between hardware
|
||||
blocks
|
||||
* provide a userspace API that allows a compositor to manage
|
||||
dependencies.
|
||||
* provide rich telemetry data to allow debugging slowdowns and stalls of
|
||||
the graphics pipeline.
|
||||
|
||||
Objects:
|
||||
* sync_timeline
|
||||
* sync_pt
|
||||
* sync_fence
|
||||
|
||||
sync_timeline:
|
||||
|
||||
A sync_timeline is an abstract monotonically increasing counter. In general,
|
||||
each driver/hardware block context will have one of these. They can be backed
|
||||
by the appropriate hardware or rely on the generic sw_sync implementation.
|
||||
Timelines are only ever created through their specific implementations
|
||||
(i.e. sw_sync.)
|
||||
|
||||
sync_pt:
|
||||
|
||||
A sync_pt is an abstract value which marks a point on a sync_timeline. Sync_pts
|
||||
have a single timeline parent. They have 3 states: active, signaled, and error.
|
||||
They start in active state and transition, once, to either signaled (when the
|
||||
timeline counter advances beyond the sync_pt’s value) or error state.
|
||||
|
||||
sync_fence:
|
||||
|
||||
Sync_fences are the primary primitives used by drivers to coordinate
|
||||
synchronization of their buffers. They are a collection of sync_pts which may
|
||||
or may not have the same timeline parent. A sync_pt can only exist in one fence
|
||||
and the fence's list of sync_pts is immutable once created. Fences can be
|
||||
waited on synchronously or asynchronously. Two fences can also be merged to
|
||||
create a third fence containing a copy of the two fences’ sync_pts. Fences are
|
||||
backed by file descriptors to allow userspace to coordinate the display pipeline
|
||||
dependencies.
|
||||
|
||||
Use:
|
||||
|
||||
A driver implementing sync support should have a work submission function which:
|
||||
* takes a fence argument specifying when to begin work
|
||||
* asynchronously queues that work to kick off when the fence is signaled
|
||||
* returns a fence to indicate when its work will be done.
|
||||
* signals the returned fence once the work is completed.
|
||||
|
||||
Consider an imaginary display driver that has the following API:
|
||||
/*
|
||||
* assumes buf is ready to be displayed.
|
||||
* blocks until the buffer is on screen.
|
||||
*/
|
||||
void display_buffer(struct dma_buf *buf);
|
||||
|
||||
The new API will become:
|
||||
/*
|
||||
* will display buf when fence is signaled.
|
||||
* returns immediately with a fence that will signal when buf
|
||||
* is no longer displayed.
|
||||
*/
|
||||
struct sync_fence* display_buffer(struct dma_buf *buf,
|
||||
struct sync_fence *fence);
|
||||
|
|
@ -30,6 +30,7 @@ Currently, these files are in /proc/sys/vm:
|
|||
- dirty_writeback_centisecs
|
||||
- drop_caches
|
||||
- extfrag_threshold
|
||||
- extra_free_kbytes
|
||||
- hugepages_treat_as_movable
|
||||
- hugetlb_shm_group
|
||||
- laptop_mode
|
||||
|
|
@ -42,6 +43,8 @@ Currently, these files are in /proc/sys/vm:
|
|||
- min_slab_ratio
|
||||
- min_unmapped_ratio
|
||||
- mmap_min_addr
|
||||
- mmap_rnd_bits
|
||||
- mmap_rnd_compat_bits
|
||||
- nr_hugepages
|
||||
- nr_overcommit_hugepages
|
||||
- nr_trim_pages (only if CONFIG_MMU=n)
|
||||
|
|
@ -236,6 +239,21 @@ fragmentation index is <= extfrag_threshold. The default value is 500.
|
|||
|
||||
==============================================================
|
||||
|
||||
extra_free_kbytes
|
||||
|
||||
This parameter tells the VM to keep extra free memory between the threshold
|
||||
where background reclaim (kswapd) kicks in, and the threshold where direct
|
||||
reclaim (by allocating processes) kicks in.
|
||||
|
||||
This is useful for workloads that require low latency memory allocations
|
||||
and have a bounded burstiness in memory allocations, for example a
|
||||
realtime application that receives and transmits network traffic
|
||||
(causing in-kernel memory allocations) with a maximum total message burst
|
||||
size of 200MB may need 200MB of extra free memory to avoid direct reclaim
|
||||
related latencies.
|
||||
|
||||
==============================================================
|
||||
|
||||
hugepages_treat_as_movable
|
||||
|
||||
This parameter controls whether we can allocate hugepages from ZONE_MOVABLE
|
||||
|
|
@ -485,6 +503,33 @@ against future potential kernel bugs.
|
|||
|
||||
==============================================================
|
||||
|
||||
mmap_rnd_bits:
|
||||
|
||||
This value can be used to select the number of bits to use to
|
||||
determine the random offset to the base address of vma regions
|
||||
resulting from mmap allocations on architectures which support
|
||||
tuning address space randomization. This value will be bounded
|
||||
by the architecture's minimum and maximum supported values.
|
||||
|
||||
This value can be changed after boot using the
|
||||
/proc/sys/vm/mmap_rnd_bits tunable
|
||||
|
||||
==============================================================
|
||||
|
||||
mmap_rnd_compat_bits:
|
||||
|
||||
This value can be used to select the number of bits to use to
|
||||
determine the random offset to the base address of vma regions
|
||||
resulting from mmap allocations for applications run in
|
||||
compatibility mode on architectures which support tuning address
|
||||
space randomization. This value will be bounded by the
|
||||
architecture's minimum and maximum supported values.
|
||||
|
||||
This value can be changed after boot using the
|
||||
/proc/sys/vm/mmap_rnd_compat_bits tunable
|
||||
|
||||
==============================================================
|
||||
|
||||
nr_hugepages
|
||||
|
||||
Change the minimum size of the hugepage pool.
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ cpufreq.
|
|||
|
||||
cpu_idle "state=%lu cpu_id=%lu"
|
||||
cpu_frequency "state=%lu cpu_id=%lu"
|
||||
cpu_frequency_limits "min=%lu max=%lu cpu_id=%lu"
|
||||
|
||||
A suspend event is used to indicate the system going in and out of the
|
||||
suspend mode:
|
||||
|
|
|
|||
|
|
@ -2088,6 +2088,35 @@ will produce:
|
|||
1) 1.449 us | }
|
||||
|
||||
|
||||
You can disable the hierarchical function call formatting and instead print a
|
||||
flat list of function entry and return events. This uses the format described
|
||||
in the Output Formatting section and respects all the trace options that
|
||||
control that formatting. Hierarchical formatting is the default.
|
||||
|
||||
hierachical: echo nofuncgraph-flat > trace_options
|
||||
flat: echo funcgraph-flat > trace_options
|
||||
|
||||
ie:
|
||||
|
||||
# tracer: function_graph
|
||||
#
|
||||
# entries-in-buffer/entries-written: 68355/68355 #P:2
|
||||
#
|
||||
# _-----=> irqs-off
|
||||
# / _----=> need-resched
|
||||
# | / _---=> hardirq/softirq
|
||||
# || / _--=> preempt-depth
|
||||
# ||| / delay
|
||||
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
|
||||
# | | | |||| | |
|
||||
sh-1806 [001] d... 198.843443: graph_ent: func=_raw_spin_lock
|
||||
sh-1806 [001] d... 198.843445: graph_ent: func=__raw_spin_lock
|
||||
sh-1806 [001] d..1 198.843447: graph_ret: func=__raw_spin_lock
|
||||
sh-1806 [001] d..1 198.843449: graph_ret: func=_raw_spin_lock
|
||||
sh-1806 [001] d..1 198.843451: graph_ent: func=_raw_spin_unlock_irqrestore
|
||||
sh-1806 [001] d... 198.843453: graph_ret: func=_raw_spin_unlock_irqrestore
|
||||
|
||||
|
||||
You might find other useful features for this tracer in the
|
||||
following "dynamic ftrace" section such as tracing only specific
|
||||
functions or tasks.
|
||||
|
|
|
|||
15
android/configs/README
Normal file
15
android/configs/README
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
The files in this directory are meant to be used as a base for an Android
|
||||
kernel config. All devices should have the options in android-base.cfg enabled.
|
||||
While not mandatory, the options in android-recommended.cfg enable advanced
|
||||
Android features.
|
||||
|
||||
Assuming you already have a minimalist defconfig for your device, a possible
|
||||
way to enable these options would be:
|
||||
|
||||
ARCH=<arch> scripts/kconfig/merge_config.sh <path_to>/<device>_defconfig android/configs/android-base.cfg android/configs/android-recommended.cfg
|
||||
|
||||
This will generate a .config that can then be used to save a new defconfig or
|
||||
compile a new kernel with Android features enabled.
|
||||
|
||||
Because there is no tool to consistently generate these config fragments,
|
||||
lets keep them alphabetically sorted instead of random.
|
||||
162
android/configs/android-base.cfg
Normal file
162
android/configs/android-base.cfg
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
# KEEP ALPHABETICALLY SORTED
|
||||
# CONFIG_DEVKMEM is not set
|
||||
# CONFIG_DEVMEM is not set
|
||||
# CONFIG_INET_LRO is not set
|
||||
# CONFIG_MODULES is not set
|
||||
# CONFIG_OABI_COMPAT is not set
|
||||
# CONFIG_SYSVIPC is not set
|
||||
CONFIG_ANDROID=y
|
||||
CONFIG_ANDROID_BINDER_IPC=y
|
||||
CONFIG_ANDROID_LOW_MEMORY_KILLER=y
|
||||
CONFIG_ARMV8_DEPRECATED=y
|
||||
CONFIG_ASHMEM=y
|
||||
CONFIG_AUDIT=y
|
||||
CONFIG_BLK_DEV_DM=y
|
||||
CONFIG_BLK_DEV_INITRD=y
|
||||
CONFIG_CGROUPS=y
|
||||
CONFIG_CGROUP_CPUACCT=y
|
||||
CONFIG_CGROUP_DEBUG=y
|
||||
CONFIG_CGROUP_FREEZER=y
|
||||
CONFIG_CGROUP_SCHED=y
|
||||
CONFIG_CP15_BARRIER_EMULATION=y
|
||||
CONFIG_DM_CRYPT=y
|
||||
CONFIG_DM_VERITY=y
|
||||
CONFIG_EMBEDDED=y
|
||||
CONFIG_FB=y
|
||||
CONFIG_HIGH_RES_TIMERS=y
|
||||
CONFIG_INET6_AH=y
|
||||
CONFIG_INET6_ESP=y
|
||||
CONFIG_INET6_IPCOMP=y
|
||||
CONFIG_INET=y
|
||||
CONFIG_INET_ESP=y
|
||||
CONFIG_INET_XFRM_MODE_TUNNEL=y
|
||||
CONFIG_IP6_NF_FILTER=y
|
||||
CONFIG_IP6_NF_IPTABLES=y
|
||||
CONFIG_IP6_NF_MANGLE=y
|
||||
CONFIG_IP6_NF_RAW=y
|
||||
CONFIG_IP6_NF_TARGET_REJECT=y
|
||||
CONFIG_IP6_NF_TARGET_REJECT_SKERR=y
|
||||
CONFIG_IPV6=y
|
||||
CONFIG_IPV6_MIP6=y
|
||||
CONFIG_IPV6_MULTIPLE_TABLES=y
|
||||
CONFIG_IPV6_OPTIMISTIC_DAD=y
|
||||
CONFIG_IPV6_PRIVACY=y
|
||||
CONFIG_IPV6_ROUTER_PREF=y
|
||||
CONFIG_IPV6_ROUTE_INFO=y
|
||||
CONFIG_IP_ADVANCED_ROUTER=y
|
||||
CONFIG_IP_MULTICAST=y
|
||||
CONFIG_IP_MULTIPLE_TABLES=y
|
||||
CONFIG_IP_NF_ARPFILTER=y
|
||||
CONFIG_IP_NF_ARPTABLES=y
|
||||
CONFIG_IP_NF_ARP_MANGLE=y
|
||||
CONFIG_IP_NF_FILTER=y
|
||||
CONFIG_IP_NF_IPTABLES=y
|
||||
CONFIG_IP_NF_MANGLE=y
|
||||
CONFIG_IP_NF_MATCH_AH=y
|
||||
CONFIG_IP_NF_MATCH_ECN=y
|
||||
CONFIG_IP_NF_MATCH_TTL=y
|
||||
CONFIG_IP_NF_RAW=y
|
||||
CONFIG_IP_NF_SECURITY=y
|
||||
CONFIG_IP_NF_TARGET_MASQUERADE=y
|
||||
CONFIG_IP_NF_TARGET_NETMAP=y
|
||||
CONFIG_IP_NF_TARGET_REDIRECT=y
|
||||
CONFIG_IP_NF_TARGET_REJECT=y
|
||||
CONFIG_IP_NF_TARGET_REJECT_SKERR=y
|
||||
CONFIG_NET=y
|
||||
CONFIG_NETDEVICES=y
|
||||
CONFIG_NETFILTER=y
|
||||
CONFIG_NETFILTER_TPROXY=y
|
||||
CONFIG_NETFILTER_XT_MATCH_COMMENT=y
|
||||
CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=y
|
||||
CONFIG_NETFILTER_XT_MATCH_CONNMARK=y
|
||||
CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y
|
||||
CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=y
|
||||
CONFIG_NETFILTER_XT_MATCH_HELPER=y
|
||||
CONFIG_NETFILTER_XT_MATCH_IPRANGE=y
|
||||
CONFIG_NETFILTER_XT_MATCH_LENGTH=y
|
||||
CONFIG_NETFILTER_XT_MATCH_LIMIT=y
|
||||
CONFIG_NETFILTER_XT_MATCH_MAC=y
|
||||
CONFIG_NETFILTER_XT_MATCH_MARK=y
|
||||
CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y
|
||||
CONFIG_NETFILTER_XT_MATCH_POLICY=y
|
||||
CONFIG_NETFILTER_XT_MATCH_QTAGUID=y
|
||||
CONFIG_NETFILTER_XT_MATCH_QUOTA2=y
|
||||
CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG=y
|
||||
CONFIG_NETFILTER_XT_MATCH_QUOTA=y
|
||||
CONFIG_NETFILTER_XT_MATCH_SOCKET=y
|
||||
CONFIG_NETFILTER_XT_MATCH_STATE=y
|
||||
CONFIG_NETFILTER_XT_MATCH_STATISTIC=y
|
||||
CONFIG_NETFILTER_XT_MATCH_STRING=y
|
||||
CONFIG_NETFILTER_XT_MATCH_TIME=y
|
||||
CONFIG_NETFILTER_XT_MATCH_U32=y
|
||||
CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y
|
||||
CONFIG_NETFILTER_XT_TARGET_CONNMARK=y
|
||||
CONFIG_NETFILTER_XT_TARGET_CONNSECMARK=y
|
||||
CONFIG_NETFILTER_XT_TARGET_IDLETIMER=y
|
||||
CONFIG_NETFILTER_XT_TARGET_MARK=y
|
||||
CONFIG_NETFILTER_XT_TARGET_NFLOG=y
|
||||
CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y
|
||||
CONFIG_NETFILTER_XT_TARGET_SECMARK=y
|
||||
CONFIG_NETFILTER_XT_TARGET_TCPMSS=y
|
||||
CONFIG_NETFILTER_XT_TARGET_TPROXY=y
|
||||
CONFIG_NETFILTER_XT_TARGET_TRACE=y
|
||||
CONFIG_NET_CLS_ACT=y
|
||||
CONFIG_NET_CLS_U32=y
|
||||
CONFIG_NET_EMATCH=y
|
||||
CONFIG_NET_EMATCH_U32=y
|
||||
CONFIG_NET_KEY=y
|
||||
CONFIG_NET_SCHED=y
|
||||
CONFIG_NET_SCH_HTB=y
|
||||
CONFIG_NF_CONNTRACK=y
|
||||
CONFIG_NF_CONNTRACK_AMANDA=y
|
||||
CONFIG_NF_CONNTRACK_EVENTS=y
|
||||
CONFIG_NF_CONNTRACK_FTP=y
|
||||
CONFIG_NF_CONNTRACK_H323=y
|
||||
CONFIG_NF_CONNTRACK_IPV4=y
|
||||
CONFIG_NF_CONNTRACK_IPV6=y
|
||||
CONFIG_NF_CONNTRACK_IRC=y
|
||||
CONFIG_NF_CONNTRACK_NETBIOS_NS=y
|
||||
CONFIG_NF_CONNTRACK_PPTP=y
|
||||
CONFIG_NF_CONNTRACK_SANE=y
|
||||
CONFIG_NF_CONNTRACK_SECMARK=y
|
||||
CONFIG_NF_CONNTRACK_TFTP=y
|
||||
CONFIG_NF_CT_NETLINK=y
|
||||
CONFIG_NF_CT_PROTO_DCCP=y
|
||||
CONFIG_NF_CT_PROTO_SCTP=y
|
||||
CONFIG_NF_CT_PROTO_UDPLITE=y
|
||||
CONFIG_NF_NAT=y
|
||||
CONFIG_NO_HZ=y
|
||||
CONFIG_PACKET=y
|
||||
CONFIG_PM_AUTOSLEEP=y
|
||||
CONFIG_PM_WAKELOCKS=y
|
||||
CONFIG_PPP=y
|
||||
CONFIG_PPPOLAC=y
|
||||
CONFIG_PPPOPNS=y
|
||||
CONFIG_PPP_BSDCOMP=y
|
||||
CONFIG_PPP_DEFLATE=y
|
||||
CONFIG_PPP_MPPE=y
|
||||
CONFIG_PREEMPT=y
|
||||
CONFIG_RESOURCE_COUNTERS=y
|
||||
CONFIG_RTC_CLASS=y
|
||||
CONFIG_RT_GROUP_SCHED=y
|
||||
CONFIG_SECURITY=y
|
||||
CONFIG_SECURITY_NETWORK=y
|
||||
CONFIG_SECURITY_SELINUX=y
|
||||
CONFIG_SETEND_EMULATION=y
|
||||
CONFIG_STAGING=y
|
||||
CONFIG_SWITCH=y
|
||||
CONFIG_SWP_EMULATION=y
|
||||
CONFIG_SYNC=y
|
||||
CONFIG_TUN=y
|
||||
CONFIG_UNIX=y
|
||||
CONFIG_USB_GADGET=y
|
||||
CONFIG_USB_CONFIGFS=y
|
||||
CONFIG_USB_CONFIGFS_F_FS=y
|
||||
CONFIG_USB_CONFIGFS_F_MTP=y
|
||||
CONFIG_USB_CONFIGFS_F_PTP=y
|
||||
CONFIG_USB_CONFIGFS_F_ACC=y
|
||||
CONFIG_USB_CONFIGFS_F_AUDIO_SRC=y
|
||||
CONFIG_USB_CONFIGFS_UEVENT=y
|
||||
CONFIG_USB_CONFIGFS_F_MIDI=y
|
||||
CONFIG_USB_OTG_WAKELOCK=y
|
||||
CONFIG_XFRM_USER=y
|
||||
127
android/configs/android-recommended.cfg
Normal file
127
android/configs/android-recommended.cfg
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
# KEEP ALPHABETICALLY SORTED
|
||||
# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set
|
||||
# CONFIG_INPUT_MOUSE is not set
|
||||
# CONFIG_LEGACY_PTYS is not set
|
||||
# CONFIG_NF_CONNTRACK_SIP is not set
|
||||
# CONFIG_PM_WAKELOCKS_GC is not set
|
||||
# CONFIG_VT is not set
|
||||
CONFIG_ANDROID_TIMED_GPIO=y
|
||||
CONFIG_ARM_KERNMEM_PERMS=y
|
||||
CONFIG_BACKLIGHT_LCD_SUPPORT=y
|
||||
CONFIG_BLK_DEV_LOOP=y
|
||||
CONFIG_BLK_DEV_RAM=y
|
||||
CONFIG_BLK_DEV_RAM_SIZE=8192
|
||||
CONFIG_COMPACTION=y
|
||||
CONFIG_DEBUG_RODATA=y
|
||||
CONFIG_DM_UEVENT=y
|
||||
CONFIG_DRAGONRISE_FF=y
|
||||
CONFIG_ENABLE_DEFAULT_TRACERS=y
|
||||
CONFIG_EXT4_FS=y
|
||||
CONFIG_EXT4_FS_SECURITY=y
|
||||
CONFIG_FUSE_FS=y
|
||||
CONFIG_GREENASIA_FF=y
|
||||
CONFIG_HIDRAW=y
|
||||
CONFIG_HID_A4TECH=y
|
||||
CONFIG_HID_ACRUX=y
|
||||
CONFIG_HID_ACRUX_FF=y
|
||||
CONFIG_HID_APPLE=y
|
||||
CONFIG_HID_BELKIN=y
|
||||
CONFIG_HID_CHERRY=y
|
||||
CONFIG_HID_CHICONY=y
|
||||
CONFIG_HID_CYPRESS=y
|
||||
CONFIG_HID_DRAGONRISE=y
|
||||
CONFIG_HID_ELECOM=y
|
||||
CONFIG_HID_EMS_FF=y
|
||||
CONFIG_HID_EZKEY=y
|
||||
CONFIG_HID_GREENASIA=y
|
||||
CONFIG_HID_GYRATION=y
|
||||
CONFIG_HID_HOLTEK=y
|
||||
CONFIG_HID_KENSINGTON=y
|
||||
CONFIG_HID_KEYTOUCH=y
|
||||
CONFIG_HID_KYE=y
|
||||
CONFIG_HID_LCPOWER=y
|
||||
CONFIG_HID_LOGITECH=y
|
||||
CONFIG_HID_LOGITECH_DJ=y
|
||||
CONFIG_HID_MAGICMOUSE=y
|
||||
CONFIG_HID_MICROSOFT=y
|
||||
CONFIG_HID_MONTEREY=y
|
||||
CONFIG_HID_MULTITOUCH=y
|
||||
CONFIG_HID_NTRIG=y
|
||||
CONFIG_HID_ORTEK=y
|
||||
CONFIG_HID_PANTHERLORD=y
|
||||
CONFIG_HID_PETALYNX=y
|
||||
CONFIG_HID_PICOLCD=y
|
||||
CONFIG_HID_PRIMAX=y
|
||||
CONFIG_HID_PRODIKEYS=y
|
||||
CONFIG_HID_ROCCAT=y
|
||||
CONFIG_HID_SAITEK=y
|
||||
CONFIG_HID_SAMSUNG=y
|
||||
CONFIG_HID_SMARTJOYPLUS=y
|
||||
CONFIG_HID_SONY=y
|
||||
CONFIG_HID_SPEEDLINK=y
|
||||
CONFIG_HID_SUNPLUS=y
|
||||
CONFIG_HID_THRUSTMASTER=y
|
||||
CONFIG_HID_TIVO=y
|
||||
CONFIG_HID_TOPSEED=y
|
||||
CONFIG_HID_TWINHAN=y
|
||||
CONFIG_HID_UCLOGIC=y
|
||||
CONFIG_HID_WACOM=y
|
||||
CONFIG_HID_WALTOP=y
|
||||
CONFIG_HID_WIIMOTE=y
|
||||
CONFIG_HID_ZEROPLUS=y
|
||||
CONFIG_HID_ZYDACRON=y
|
||||
CONFIG_INPUT_EVDEV=y
|
||||
CONFIG_INPUT_GPIO=y
|
||||
CONFIG_INPUT_JOYSTICK=y
|
||||
CONFIG_INPUT_KEYCHORD=y
|
||||
CONFIG_INPUT_KEYRESET=y
|
||||
CONFIG_INPUT_MISC=y
|
||||
CONFIG_INPUT_TABLET=y
|
||||
CONFIG_INPUT_UINPUT=y
|
||||
CONFIG_ION=y
|
||||
CONFIG_JOYSTICK_XPAD=y
|
||||
CONFIG_JOYSTICK_XPAD_FF=y
|
||||
CONFIG_JOYSTICK_XPAD_LEDS=y
|
||||
CONFIG_KALLSYMS_ALL=y
|
||||
CONFIG_KSM=y
|
||||
CONFIG_LOGIG940_FF=y
|
||||
CONFIG_LOGIRUMBLEPAD2_FF=y
|
||||
CONFIG_LOGITECH_FF=y
|
||||
CONFIG_MD=y
|
||||
CONFIG_MEDIA_SUPPORT=y
|
||||
CONFIG_MSDOS_FS=y
|
||||
CONFIG_PANIC_TIMEOUT=5
|
||||
CONFIG_PANTHERLORD_FF=y
|
||||
CONFIG_PERF_EVENTS=y
|
||||
CONFIG_PM_DEBUG=y
|
||||
CONFIG_PM_RUNTIME=y
|
||||
CONFIG_PM_WAKELOCKS_LIMIT=0
|
||||
CONFIG_POWER_SUPPLY=y
|
||||
CONFIG_PSTORE=y
|
||||
CONFIG_PSTORE_CONSOLE=y
|
||||
CONFIG_PSTORE_RAM=y
|
||||
CONFIG_SCHEDSTATS=y
|
||||
CONFIG_SMARTJOYPLUS_FF=y
|
||||
CONFIG_SND=y
|
||||
CONFIG_SOUND=y
|
||||
CONFIG_SUSPEND_TIME=y
|
||||
CONFIG_TABLET_USB_ACECAD=y
|
||||
CONFIG_TABLET_USB_AIPTEK=y
|
||||
CONFIG_TABLET_USB_GTCO=y
|
||||
CONFIG_TABLET_USB_HANWANG=y
|
||||
CONFIG_TABLET_USB_KBTAB=y
|
||||
CONFIG_TABLET_USB_WACOM=y
|
||||
CONFIG_TASKSTATS=y
|
||||
CONFIG_TASK_DELAY_ACCT=y
|
||||
CONFIG_TASK_IO_ACCOUNTING=y
|
||||
CONFIG_TASK_XACCT=y
|
||||
CONFIG_TIMER_STATS=y
|
||||
CONFIG_TMPFS=y
|
||||
CONFIG_TMPFS_POSIX_ACL=y
|
||||
CONFIG_UHID=y
|
||||
CONFIG_UID_STAT=y
|
||||
CONFIG_USB_ANNOUNCE_NEW_DEVICES=y
|
||||
CONFIG_USB_EHCI_HCD=y
|
||||
CONFIG_USB_HIDDEV=y
|
||||
CONFIG_USB_USBNET=y
|
||||
CONFIG_VFAT_FS=y
|
||||
68
arch/Kconfig
68
arch/Kconfig
|
|
@ -518,6 +518,74 @@ config HAVE_COPY_THREAD_TLS
|
|||
normal C parameter passing, rather than extracting the syscall
|
||||
argument from pt_regs.
|
||||
|
||||
config HAVE_ARCH_MMAP_RND_BITS
|
||||
bool
|
||||
help
|
||||
An arch should select this symbol if it supports setting a variable
|
||||
number of bits for use in establishing the base address for mmap
|
||||
allocations, has MMU enabled and provides values for both:
|
||||
- ARCH_MMAP_RND_BITS_MIN
|
||||
- ARCH_MMAP_RND_BITS_MAX
|
||||
|
||||
config ARCH_MMAP_RND_BITS_MIN
|
||||
int
|
||||
|
||||
config ARCH_MMAP_RND_BITS_MAX
|
||||
int
|
||||
|
||||
config ARCH_MMAP_RND_BITS_DEFAULT
|
||||
int
|
||||
|
||||
config ARCH_MMAP_RND_BITS
|
||||
int "Number of bits to use for ASLR of mmap base address" if EXPERT
|
||||
range ARCH_MMAP_RND_BITS_MIN ARCH_MMAP_RND_BITS_MAX
|
||||
default ARCH_MMAP_RND_BITS_DEFAULT if ARCH_MMAP_RND_BITS_DEFAULT
|
||||
default ARCH_MMAP_RND_BITS_MIN
|
||||
depends on HAVE_ARCH_MMAP_RND_BITS
|
||||
help
|
||||
This value can be used to select the number of bits to use to
|
||||
determine the random offset to the base address of vma regions
|
||||
resulting from mmap allocations. This value will be bounded
|
||||
by the architecture's minimum and maximum supported values.
|
||||
|
||||
This value can be changed after boot using the
|
||||
/proc/sys/vm/mmap_rnd_bits tunable
|
||||
|
||||
config HAVE_ARCH_MMAP_RND_COMPAT_BITS
|
||||
bool
|
||||
help
|
||||
An arch should select this symbol if it supports running applications
|
||||
in compatibility mode, supports setting a variable number of bits for
|
||||
use in establishing the base address for mmap allocations, has MMU
|
||||
enabled and provides values for both:
|
||||
- ARCH_MMAP_RND_COMPAT_BITS_MIN
|
||||
- ARCH_MMAP_RND_COMPAT_BITS_MAX
|
||||
|
||||
config ARCH_MMAP_RND_COMPAT_BITS_MIN
|
||||
int
|
||||
|
||||
config ARCH_MMAP_RND_COMPAT_BITS_MAX
|
||||
int
|
||||
|
||||
config ARCH_MMAP_RND_COMPAT_BITS_DEFAULT
|
||||
int
|
||||
|
||||
config ARCH_MMAP_RND_COMPAT_BITS
|
||||
int "Number of bits to use for ASLR of mmap base address for compatible applications" if EXPERT
|
||||
range ARCH_MMAP_RND_COMPAT_BITS_MIN ARCH_MMAP_RND_COMPAT_BITS_MAX
|
||||
default ARCH_MMAP_RND_COMPAT_BITS_DEFAULT if ARCH_MMAP_RND_COMPAT_BITS_DEFAULT
|
||||
default ARCH_MMAP_RND_COMPAT_BITS_MIN
|
||||
depends on HAVE_ARCH_MMAP_RND_COMPAT_BITS
|
||||
help
|
||||
This value can be used to select the number of bits to use to
|
||||
determine the random offset to the base address of vma regions
|
||||
resulting from mmap allocations for compatible applications This
|
||||
value will be bounded by the architecture's minimum and maximum
|
||||
supported values.
|
||||
|
||||
This value can be changed after boot using the
|
||||
/proc/sys/vm/mmap_rnd_compat_bits tunable
|
||||
|
||||
#
|
||||
# ABI hall of shame
|
||||
#
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ config ARM
|
|||
select HAVE_ARCH_BITREVERSE if (CPU_32v7M || CPU_32v7) && !CPU_32v6
|
||||
select HAVE_ARCH_JUMP_LABEL if !XIP_KERNEL && !CPU_ENDIAN_BE32
|
||||
select HAVE_ARCH_KGDB if !CPU_ENDIAN_BE32
|
||||
select HAVE_ARCH_MMAP_RND_BITS if MMU
|
||||
select HAVE_ARCH_SECCOMP_FILTER if (AEABI && !OABI_COMPAT)
|
||||
select HAVE_ARCH_TRACEHOOK
|
||||
select HAVE_BPF_JIT
|
||||
|
|
@ -308,6 +309,14 @@ config MMU
|
|||
Select if you want MMU-based virtualised addressing space
|
||||
support by paged memory management. If unsure, say 'Y'.
|
||||
|
||||
config ARCH_MMAP_RND_BITS_MIN
|
||||
default 8
|
||||
|
||||
config ARCH_MMAP_RND_BITS_MAX
|
||||
default 14 if PAGE_OFFSET=0x40000000
|
||||
default 15 if PAGE_OFFSET=0x80000000
|
||||
default 16
|
||||
|
||||
#
|
||||
# The "ARM system type" choice list is ordered alphabetically by option
|
||||
# text. Please add new entries in the option alphabetic order.
|
||||
|
|
@ -1816,6 +1825,15 @@ config XEN
|
|||
help
|
||||
Say Y if you want to run Linux in a Virtual Machine on Xen on ARM.
|
||||
|
||||
config ARM_FLUSH_CONSOLE_ON_RESTART
|
||||
bool "Force flush the console on restart"
|
||||
help
|
||||
If the console is locked while the system is rebooted, the messages
|
||||
in the temporary logbuffer would not have propogated to all the
|
||||
console drivers. This option forces the console lock to be
|
||||
released if it failed to be acquired, which will cause all the
|
||||
pending messages to be flushed.
|
||||
|
||||
endmenu
|
||||
|
||||
menu "Boot options"
|
||||
|
|
@ -1844,6 +1862,21 @@ config DEPRECATED_PARAM_STRUCT
|
|||
This was deprecated in 2001 and announced to live on for 5 years.
|
||||
Some old boot loaders still use this way.
|
||||
|
||||
config BUILD_ARM_APPENDED_DTB_IMAGE
|
||||
bool "Build a concatenated zImage/dtb by default"
|
||||
depends on OF
|
||||
help
|
||||
Enabling this option will cause a concatenated zImage and list of
|
||||
DTBs to be built by default (instead of a standalone zImage.)
|
||||
The image will built in arch/arm/boot/zImage-dtb
|
||||
|
||||
config BUILD_ARM_APPENDED_DTB_IMAGE_NAMES
|
||||
string "Default dtb names"
|
||||
depends on BUILD_ARM_APPENDED_DTB_IMAGE
|
||||
help
|
||||
Space separated list of names of dtbs to append when
|
||||
building a concatenated zImage-dtb.
|
||||
|
||||
# Compressed boot loader in ROM. Yes, we really want to ask about
|
||||
# TEXT and BSS so we preserve their values in the config files.
|
||||
config ZBOOT_ROM_TEXT
|
||||
|
|
|
|||
|
|
@ -1623,6 +1623,14 @@ config EARLY_PRINTK
|
|||
kernel low-level debugging functions. Add earlyprintk to your
|
||||
kernel parameters to enable this console.
|
||||
|
||||
config EARLY_PRINTK_DIRECT
|
||||
bool "Early printk direct"
|
||||
depends on DEBUG_LL
|
||||
help
|
||||
Say Y here if you want to have an early console using the
|
||||
kernel low-level debugging functions and EARLY_PRINTK is
|
||||
not early enough.
|
||||
|
||||
config ARM_KPROBES_TEST
|
||||
tristate "Kprobes test module"
|
||||
depends on KPROBES && MODULES
|
||||
|
|
|
|||
|
|
@ -296,6 +296,8 @@ libs-y := arch/arm/lib/ $(libs-y)
|
|||
# Default target when executing plain make
|
||||
ifeq ($(CONFIG_XIP_KERNEL),y)
|
||||
KBUILD_IMAGE := xipImage
|
||||
else ifeq ($(CONFIG_BUILD_ARM_APPENDED_DTB_IMAGE),y)
|
||||
KBUILD_IMAGE := zImage-dtb
|
||||
else
|
||||
KBUILD_IMAGE := zImage
|
||||
endif
|
||||
|
|
@ -346,6 +348,9 @@ ifeq ($(CONFIG_VDSO),y)
|
|||
$(Q)$(MAKE) $(build)=arch/arm/vdso $@
|
||||
endif
|
||||
|
||||
zImage-dtb: vmlinux scripts dtbs
|
||||
$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
|
||||
|
||||
# We use MRPROPER_FILES and CLEAN_FILES now
|
||||
archclean:
|
||||
$(Q)$(MAKE) $(clean)=$(boot)
|
||||
|
|
|
|||
1
arch/arm/boot/.gitignore
vendored
1
arch/arm/boot/.gitignore
vendored
|
|
@ -4,3 +4,4 @@ xipImage
|
|||
bootpImage
|
||||
uImage
|
||||
*.dtb
|
||||
zImage-dtb
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
ifneq ($(MACHINE),)
|
||||
include $(MACHINE)/Makefile.boot
|
||||
endif
|
||||
include $(srctree)/arch/arm/boot/dts/Makefile
|
||||
|
||||
# Note: the following conditions must always be true:
|
||||
# ZRELADDR == virt_to_phys(PAGE_OFFSET + TEXT_OFFSET)
|
||||
|
|
@ -27,6 +28,14 @@ export ZRELADDR INITRD_PHYS PARAMS_PHYS
|
|||
|
||||
targets := Image zImage xipImage bootpImage uImage
|
||||
|
||||
DTB_NAMES := $(subst $\",,$(CONFIG_BUILD_ARM_APPENDED_DTB_IMAGE_NAMES))
|
||||
ifneq ($(DTB_NAMES),)
|
||||
DTB_LIST := $(addsuffix .dtb,$(DTB_NAMES))
|
||||
else
|
||||
DTB_LIST := $(dtb-y)
|
||||
endif
|
||||
DTB_OBJS := $(addprefix $(obj)/dts/,$(DTB_LIST))
|
||||
|
||||
ifeq ($(CONFIG_XIP_KERNEL),y)
|
||||
|
||||
$(obj)/xipImage: vmlinux FORCE
|
||||
|
|
@ -55,6 +64,10 @@ $(obj)/zImage: $(obj)/compressed/vmlinux FORCE
|
|||
$(call if_changed,objcopy)
|
||||
@$(kecho) ' Kernel: $@ is ready'
|
||||
|
||||
$(obj)/zImage-dtb: $(obj)/zImage $(DTB_OBJS) FORCE
|
||||
$(call if_changed,cat)
|
||||
@echo ' Kernel: $@ is ready'
|
||||
|
||||
endif
|
||||
|
||||
ifneq ($(LOADADDR),)
|
||||
|
|
|
|||
|
|
@ -778,6 +778,8 @@ __armv7_mmu_cache_on:
|
|||
bic r6, r6, #1 << 31 @ 32-bit translation system
|
||||
bic r6, r6, #3 << 0 @ use only ttbr0
|
||||
mcrne p15, 0, r3, c2, c0, 0 @ load page table pointer
|
||||
mcrne p15, 0, r0, c8, c7, 0 @ flush I,D TLBs
|
||||
mcr p15, 0, r0, c7, c5, 4 @ ISB
|
||||
mcrne p15, 0, r1, c3, c0, 0 @ load domain access control
|
||||
mcrne p15, 0, r6, c2, c0, 2 @ load ttb control
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -782,5 +782,15 @@ endif
|
|||
dtstree := $(srctree)/$(src)
|
||||
dtb-$(CONFIG_OF_ALL_DTBS) := $(patsubst $(dtstree)/%.dts,%.dtb, $(wildcard $(dtstree)/*.dts))
|
||||
|
||||
always := $(dtb-y)
|
||||
DTB_NAMES := $(subst $\",,$(CONFIG_BUILD_ARM_APPENDED_DTB_IMAGE_NAMES))
|
||||
ifneq ($(DTB_NAMES),)
|
||||
DTB_LIST := $(addsuffix .dtb,$(DTB_NAMES))
|
||||
else
|
||||
DTB_LIST := $(dtb-y)
|
||||
endif
|
||||
|
||||
targets += dtbs dtbs_install
|
||||
targets += $(DTB_LIST)
|
||||
|
||||
always := $(DTB_LIST)
|
||||
clean-files := *.dtb
|
||||
|
|
|
|||
|
|
@ -17,3 +17,7 @@ config SHARP_PARAM
|
|||
|
||||
config SHARP_SCOOP
|
||||
bool
|
||||
|
||||
config FIQ_GLUE
|
||||
bool
|
||||
select FIQ
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
obj-y += firmware.o
|
||||
|
||||
obj-$(CONFIG_FIQ_GLUE) += fiq_glue.o fiq_glue_setup.o
|
||||
obj-$(CONFIG_ICST) += icst.o
|
||||
obj-$(CONFIG_SA1111) += sa1111.o
|
||||
obj-$(CONFIG_DMABOUNCE) += dmabounce.o
|
||||
|
|
|
|||
118
arch/arm/common/fiq_glue.S
Normal file
118
arch/arm/common/fiq_glue.S
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Copyright (C) 2008 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/linkage.h>
|
||||
#include <asm/assembler.h>
|
||||
|
||||
.text
|
||||
|
||||
.global fiq_glue_end
|
||||
|
||||
/* fiq stack: r0-r15,cpsr,spsr of interrupted mode */
|
||||
|
||||
ENTRY(fiq_glue)
|
||||
/* store pc, cpsr from previous mode, reserve space for spsr */
|
||||
mrs r12, spsr
|
||||
sub lr, lr, #4
|
||||
subs r10, #1
|
||||
bne nested_fiq
|
||||
|
||||
str r12, [sp, #-8]!
|
||||
str lr, [sp, #-4]!
|
||||
|
||||
/* store r8-r14 from previous mode */
|
||||
sub sp, sp, #(7 * 4)
|
||||
stmia sp, {r8-r14}^
|
||||
nop
|
||||
|
||||
/* store r0-r7 from previous mode */
|
||||
stmfd sp!, {r0-r7}
|
||||
|
||||
/* setup func(data,regs) arguments */
|
||||
mov r0, r9
|
||||
mov r1, sp
|
||||
mov r3, r8
|
||||
|
||||
mov r7, sp
|
||||
|
||||
/* Get sp and lr from non-user modes */
|
||||
and r4, r12, #MODE_MASK
|
||||
cmp r4, #USR_MODE
|
||||
beq fiq_from_usr_mode
|
||||
|
||||
mov r7, sp
|
||||
orr r4, r4, #(PSR_I_BIT | PSR_F_BIT)
|
||||
msr cpsr_c, r4
|
||||
str sp, [r7, #(4 * 13)]
|
||||
str lr, [r7, #(4 * 14)]
|
||||
mrs r5, spsr
|
||||
str r5, [r7, #(4 * 17)]
|
||||
|
||||
cmp r4, #(SVC_MODE | PSR_I_BIT | PSR_F_BIT)
|
||||
/* use fiq stack if we reenter this mode */
|
||||
subne sp, r7, #(4 * 3)
|
||||
|
||||
fiq_from_usr_mode:
|
||||
msr cpsr_c, #(SVC_MODE | PSR_I_BIT | PSR_F_BIT)
|
||||
mov r2, sp
|
||||
sub sp, r7, #12
|
||||
stmfd sp!, {r2, ip, lr}
|
||||
/* call func(data,regs) */
|
||||
blx r3
|
||||
ldmfd sp, {r2, ip, lr}
|
||||
mov sp, r2
|
||||
|
||||
/* restore/discard saved state */
|
||||
cmp r4, #USR_MODE
|
||||
beq fiq_from_usr_mode_exit
|
||||
|
||||
msr cpsr_c, r4
|
||||
ldr sp, [r7, #(4 * 13)]
|
||||
ldr lr, [r7, #(4 * 14)]
|
||||
msr spsr_cxsf, r5
|
||||
|
||||
fiq_from_usr_mode_exit:
|
||||
msr cpsr_c, #(FIQ_MODE | PSR_I_BIT | PSR_F_BIT)
|
||||
|
||||
ldmfd sp!, {r0-r7}
|
||||
ldr lr, [sp, #(4 * 7)]
|
||||
ldr r12, [sp, #(4 * 8)]
|
||||
add sp, sp, #(10 * 4)
|
||||
exit_fiq:
|
||||
msr spsr_cxsf, r12
|
||||
add r10, #1
|
||||
cmp r11, #0
|
||||
moveqs pc, lr
|
||||
bx r11 /* jump to custom fiq return function */
|
||||
|
||||
nested_fiq:
|
||||
orr r12, r12, #(PSR_F_BIT)
|
||||
b exit_fiq
|
||||
|
||||
fiq_glue_end:
|
||||
|
||||
ENTRY(fiq_glue_setup) /* func, data, sp, smc call number */
|
||||
stmfd sp!, {r4}
|
||||
mrs r4, cpsr
|
||||
msr cpsr_c, #(FIQ_MODE | PSR_I_BIT | PSR_F_BIT)
|
||||
movs r8, r0
|
||||
mov r9, r1
|
||||
mov sp, r2
|
||||
mov r11, r3
|
||||
moveq r10, #0
|
||||
movne r10, #1
|
||||
msr cpsr_c, r4
|
||||
ldmfd sp!, {r4}
|
||||
bx lr
|
||||
|
||||
147
arch/arm/common/fiq_glue_setup.c
Normal file
147
arch/arm/common/fiq_glue_setup.c
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Copyright (C) 2010 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/percpu.h>
|
||||
#include <linux/slab.h>
|
||||
#include <asm/fiq.h>
|
||||
#include <asm/fiq_glue.h>
|
||||
|
||||
extern unsigned char fiq_glue, fiq_glue_end;
|
||||
extern void fiq_glue_setup(void *func, void *data, void *sp,
|
||||
fiq_return_handler_t fiq_return_handler);
|
||||
|
||||
static struct fiq_handler fiq_debbuger_fiq_handler = {
|
||||
.name = "fiq_glue",
|
||||
};
|
||||
DEFINE_PER_CPU(void *, fiq_stack);
|
||||
static struct fiq_glue_handler *current_handler;
|
||||
static fiq_return_handler_t fiq_return_handler;
|
||||
static DEFINE_MUTEX(fiq_glue_lock);
|
||||
|
||||
static void fiq_glue_setup_helper(void *info)
|
||||
{
|
||||
struct fiq_glue_handler *handler = info;
|
||||
fiq_glue_setup(handler->fiq, handler,
|
||||
__get_cpu_var(fiq_stack) + THREAD_START_SP,
|
||||
fiq_return_handler);
|
||||
}
|
||||
|
||||
int fiq_glue_register_handler(struct fiq_glue_handler *handler)
|
||||
{
|
||||
int ret;
|
||||
int cpu;
|
||||
|
||||
if (!handler || !handler->fiq)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&fiq_glue_lock);
|
||||
if (fiq_stack) {
|
||||
ret = -EBUSY;
|
||||
goto err_busy;
|
||||
}
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
void *stack;
|
||||
stack = (void *)__get_free_pages(GFP_KERNEL, THREAD_SIZE_ORDER);
|
||||
if (WARN_ON(!stack)) {
|
||||
ret = -ENOMEM;
|
||||
goto err_alloc_fiq_stack;
|
||||
}
|
||||
per_cpu(fiq_stack, cpu) = stack;
|
||||
}
|
||||
|
||||
ret = claim_fiq(&fiq_debbuger_fiq_handler);
|
||||
if (WARN_ON(ret))
|
||||
goto err_claim_fiq;
|
||||
|
||||
current_handler = handler;
|
||||
on_each_cpu(fiq_glue_setup_helper, handler, true);
|
||||
set_fiq_handler(&fiq_glue, &fiq_glue_end - &fiq_glue);
|
||||
|
||||
mutex_unlock(&fiq_glue_lock);
|
||||
return 0;
|
||||
|
||||
err_claim_fiq:
|
||||
err_alloc_fiq_stack:
|
||||
for_each_possible_cpu(cpu) {
|
||||
__free_pages(per_cpu(fiq_stack, cpu), THREAD_SIZE_ORDER);
|
||||
per_cpu(fiq_stack, cpu) = NULL;
|
||||
}
|
||||
err_busy:
|
||||
mutex_unlock(&fiq_glue_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void fiq_glue_update_return_handler(void (*fiq_return)(void))
|
||||
{
|
||||
fiq_return_handler = fiq_return;
|
||||
if (current_handler)
|
||||
on_each_cpu(fiq_glue_setup_helper, current_handler, true);
|
||||
}
|
||||
|
||||
int fiq_glue_set_return_handler(void (*fiq_return)(void))
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&fiq_glue_lock);
|
||||
if (fiq_return_handler) {
|
||||
ret = -EBUSY;
|
||||
goto err_busy;
|
||||
}
|
||||
fiq_glue_update_return_handler(fiq_return);
|
||||
ret = 0;
|
||||
err_busy:
|
||||
mutex_unlock(&fiq_glue_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(fiq_glue_set_return_handler);
|
||||
|
||||
int fiq_glue_clear_return_handler(void (*fiq_return)(void))
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&fiq_glue_lock);
|
||||
if (WARN_ON(fiq_return_handler != fiq_return)) {
|
||||
ret = -EINVAL;
|
||||
goto err_inval;
|
||||
}
|
||||
fiq_glue_update_return_handler(NULL);
|
||||
ret = 0;
|
||||
err_inval:
|
||||
mutex_unlock(&fiq_glue_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(fiq_glue_clear_return_handler);
|
||||
|
||||
/**
|
||||
* fiq_glue_resume - Restore fiqs after suspend or low power idle states
|
||||
*
|
||||
* This must be called before calling local_fiq_enable after returning from a
|
||||
* power state where the fiq mode registers were lost. If a driver provided
|
||||
* a resume hook when it registered the handler it will be called.
|
||||
*/
|
||||
|
||||
void fiq_glue_resume(void)
|
||||
{
|
||||
if (!current_handler)
|
||||
return;
|
||||
fiq_glue_setup(current_handler->fiq, current_handler,
|
||||
__get_cpu_var(fiq_stack) + THREAD_START_SP,
|
||||
fiq_return_handler);
|
||||
if (current_handler->resume)
|
||||
current_handler->resume(current_handler);
|
||||
}
|
||||
|
||||
33
arch/arm/include/asm/fiq_glue.h
Normal file
33
arch/arm/include/asm/fiq_glue.h
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (C) 2010 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __ASM_FIQ_GLUE_H
|
||||
#define __ASM_FIQ_GLUE_H
|
||||
|
||||
struct fiq_glue_handler {
|
||||
void (*fiq)(struct fiq_glue_handler *h, void *regs, void *svc_sp);
|
||||
void (*resume)(struct fiq_glue_handler *h);
|
||||
};
|
||||
typedef void (*fiq_return_handler_t)(void);
|
||||
|
||||
int fiq_glue_register_handler(struct fiq_glue_handler *handler);
|
||||
int fiq_glue_set_return_handler(fiq_return_handler_t fiq_return);
|
||||
int fiq_glue_clear_return_handler(fiq_return_handler_t fiq_return);
|
||||
|
||||
#ifdef CONFIG_FIQ_GLUE
|
||||
void fiq_glue_resume(void);
|
||||
#else
|
||||
static inline void fiq_glue_resume(void) {}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
28
arch/arm/include/asm/mach/mmc.h
Normal file
28
arch/arm/include/asm/mach/mmc.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* arch/arm/include/asm/mach/mmc.h
|
||||
*/
|
||||
#ifndef ASMARM_MACH_MMC_H
|
||||
#define ASMARM_MACH_MMC_H
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/sdio_func.h>
|
||||
|
||||
struct embedded_sdio_data {
|
||||
struct sdio_cis cis;
|
||||
struct sdio_cccr cccr;
|
||||
struct sdio_embedded_func *funcs;
|
||||
int num_funcs;
|
||||
};
|
||||
|
||||
struct mmc_platform_data {
|
||||
unsigned int ocr_mask; /* available voltages */
|
||||
int built_in; /* built-in device flag */
|
||||
int card_present; /* card detect state */
|
||||
u32 (*translate_vdd)(struct device *, unsigned int);
|
||||
unsigned int (*status)(struct device *);
|
||||
struct embedded_sdio_data *embedded_sdio;
|
||||
int (*register_status_notify)(void (*callback)(int card_present, void *dev_id), void *dev_id);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -10,7 +10,6 @@
|
|||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
|
|
@ -41,19 +40,10 @@
|
|||
* GCC 3.2.x: miscompiles NEW_AUX_ENT in fs/binfmt_elf.c
|
||||
* (http://gcc.gnu.org/PR8896) and incorrect structure
|
||||
* initialisation in fs/jffs2/erase.c
|
||||
* GCC 4.8.0-4.8.2: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58854
|
||||
* miscompiles find_get_entry(), and can result in EXT3 and EXT4
|
||||
* filesystem corruption (possibly other FS too).
|
||||
*/
|
||||
#ifdef __GNUC__
|
||||
#if (__GNUC__ == 3 && __GNUC_MINOR__ < 3)
|
||||
#error Your compiler is too buggy; it is known to miscompile kernels.
|
||||
#error Known good compilers: 3.3, 4.x
|
||||
#endif
|
||||
#if GCC_VERSION >= 40800 && GCC_VERSION < 40803
|
||||
#error Your compiler is too buggy; it is known to miscompile kernels
|
||||
#error and result in filesystem corruption and oopses.
|
||||
#endif
|
||||
#error Known good compilers: 3.3
|
||||
#endif
|
||||
|
||||
int main(void)
|
||||
|
|
|
|||
|
|
@ -140,6 +140,8 @@ int kgdb_arch_handle_exception(int exception_vector, int signo,
|
|||
|
||||
static int kgdb_brk_fn(struct pt_regs *regs, unsigned int instr)
|
||||
{
|
||||
if (user_mode(regs))
|
||||
return -1;
|
||||
kgdb_handle_exception(1, SIGTRAP, 0, regs);
|
||||
|
||||
return 0;
|
||||
|
|
@ -147,6 +149,8 @@ static int kgdb_brk_fn(struct pt_regs *regs, unsigned int instr)
|
|||
|
||||
static int kgdb_compiled_brk_fn(struct pt_regs *regs, unsigned int instr)
|
||||
{
|
||||
if (user_mode(regs))
|
||||
return -1;
|
||||
compiled_break = 1;
|
||||
kgdb_handle_exception(1, SIGTRAP, 0, regs);
|
||||
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ void arch_cpu_idle_prepare(void)
|
|||
|
||||
void arch_cpu_idle_enter(void)
|
||||
{
|
||||
idle_notifier_call_chain(IDLE_START);
|
||||
ledtrig_cpu(CPU_LED_IDLE_START);
|
||||
#ifdef CONFIG_PL310_ERRATA_769419
|
||||
wmb();
|
||||
|
|
@ -89,6 +90,78 @@ void arch_cpu_idle_enter(void)
|
|||
void arch_cpu_idle_exit(void)
|
||||
{
|
||||
ledtrig_cpu(CPU_LED_IDLE_END);
|
||||
idle_notifier_call_chain(IDLE_END);
|
||||
}
|
||||
|
||||
/*
|
||||
* dump a block of kernel memory from around the given address
|
||||
*/
|
||||
static void show_data(unsigned long addr, int nbytes, const char *name)
|
||||
{
|
||||
int i, j;
|
||||
int nlines;
|
||||
u32 *p;
|
||||
|
||||
/*
|
||||
* don't attempt to dump non-kernel addresses or
|
||||
* values that are probably just small negative numbers
|
||||
*/
|
||||
if (addr < PAGE_OFFSET || addr > -256UL)
|
||||
return;
|
||||
|
||||
printk("\n%s: %#lx:\n", name, addr);
|
||||
|
||||
/*
|
||||
* round address down to a 32 bit boundary
|
||||
* and always dump a multiple of 32 bytes
|
||||
*/
|
||||
p = (u32 *)(addr & ~(sizeof(u32) - 1));
|
||||
nbytes += (addr & (sizeof(u32) - 1));
|
||||
nlines = (nbytes + 31) / 32;
|
||||
|
||||
|
||||
for (i = 0; i < nlines; i++) {
|
||||
/*
|
||||
* just display low 16 bits of address to keep
|
||||
* each line of the dump < 80 characters
|
||||
*/
|
||||
printk("%04lx ", (unsigned long)p & 0xffff);
|
||||
for (j = 0; j < 8; j++) {
|
||||
u32 data;
|
||||
if (probe_kernel_address(p, data)) {
|
||||
printk(" ********");
|
||||
} else {
|
||||
printk(" %08x", data);
|
||||
}
|
||||
++p;
|
||||
}
|
||||
printk("\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void show_extra_register_data(struct pt_regs *regs, int nbytes)
|
||||
{
|
||||
mm_segment_t fs;
|
||||
|
||||
fs = get_fs();
|
||||
set_fs(KERNEL_DS);
|
||||
show_data(regs->ARM_pc - nbytes, nbytes * 2, "PC");
|
||||
show_data(regs->ARM_lr - nbytes, nbytes * 2, "LR");
|
||||
show_data(regs->ARM_sp - nbytes, nbytes * 2, "SP");
|
||||
show_data(regs->ARM_ip - nbytes, nbytes * 2, "IP");
|
||||
show_data(regs->ARM_fp - nbytes, nbytes * 2, "FP");
|
||||
show_data(regs->ARM_r0 - nbytes, nbytes * 2, "R0");
|
||||
show_data(regs->ARM_r1 - nbytes, nbytes * 2, "R1");
|
||||
show_data(regs->ARM_r2 - nbytes, nbytes * 2, "R2");
|
||||
show_data(regs->ARM_r3 - nbytes, nbytes * 2, "R3");
|
||||
show_data(regs->ARM_r4 - nbytes, nbytes * 2, "R4");
|
||||
show_data(regs->ARM_r5 - nbytes, nbytes * 2, "R5");
|
||||
show_data(regs->ARM_r6 - nbytes, nbytes * 2, "R6");
|
||||
show_data(regs->ARM_r7 - nbytes, nbytes * 2, "R7");
|
||||
show_data(regs->ARM_r8 - nbytes, nbytes * 2, "R8");
|
||||
show_data(regs->ARM_r9 - nbytes, nbytes * 2, "R9");
|
||||
show_data(regs->ARM_r10 - nbytes, nbytes * 2, "R10");
|
||||
set_fs(fs);
|
||||
}
|
||||
|
||||
void __show_regs(struct pt_regs *regs)
|
||||
|
|
@ -178,6 +251,8 @@ void __show_regs(struct pt_regs *regs)
|
|||
printk("Control: %08x%s\n", ctrl, buf);
|
||||
}
|
||||
#endif
|
||||
|
||||
show_extra_register_data(regs, 128);
|
||||
}
|
||||
|
||||
void show_regs(struct pt_regs * regs)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#include <linux/console.h>
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/reboot.h>
|
||||
|
|
@ -124,6 +125,31 @@ void machine_power_off(void)
|
|||
pm_power_off();
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ARM_FLUSH_CONSOLE_ON_RESTART
|
||||
void arm_machine_flush_console(void)
|
||||
{
|
||||
printk("\n");
|
||||
pr_emerg("Restarting %s\n", linux_banner);
|
||||
if (console_trylock()) {
|
||||
console_unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
mdelay(50);
|
||||
|
||||
local_irq_disable();
|
||||
if (!console_trylock())
|
||||
pr_emerg("arm_restart: Console was locked! Busting\n");
|
||||
else
|
||||
pr_emerg("arm_restart: Console was locked!\n");
|
||||
console_unlock();
|
||||
}
|
||||
#else
|
||||
void arm_machine_flush_console(void)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Restart requires that the secondary CPUs stop performing any activity
|
||||
* while the primary CPU resets the system. Systems with a single CPU can
|
||||
|
|
@ -140,6 +166,10 @@ void machine_restart(char *cmd)
|
|||
local_irq_disable();
|
||||
smp_send_stop();
|
||||
|
||||
/* Flush the console to make sure all the relevant messages make it
|
||||
* out to the console drivers */
|
||||
arm_machine_flush_console();
|
||||
|
||||
if (arm_pm_restart)
|
||||
arm_pm_restart(reboot_mode, cmd);
|
||||
else
|
||||
|
|
|
|||
|
|
@ -270,6 +270,11 @@ v6_dma_clean_range:
|
|||
* - end - virtual end address of region
|
||||
*/
|
||||
ENTRY(v6_dma_flush_range)
|
||||
#ifdef CONFIG_CACHE_FLUSH_RANGE_LIMIT
|
||||
sub r2, r1, r0
|
||||
cmp r2, #CONFIG_CACHE_FLUSH_RANGE_LIMIT
|
||||
bhi v6_dma_flush_dcache_all
|
||||
#endif
|
||||
#ifdef CONFIG_DMA_CACHE_RWFO
|
||||
ldrb r2, [r0] @ read for ownership
|
||||
strb r2, [r0] @ write for ownership
|
||||
|
|
@ -292,6 +297,18 @@ ENTRY(v6_dma_flush_range)
|
|||
mcr p15, 0, r0, c7, c10, 4 @ drain write buffer
|
||||
ret lr
|
||||
|
||||
#ifdef CONFIG_CACHE_FLUSH_RANGE_LIMIT
|
||||
v6_dma_flush_dcache_all:
|
||||
mov r0, #0
|
||||
#ifdef HARVARD_CACHE
|
||||
mcr p15, 0, r0, c7, c14, 0 @ D cache clean+invalidate
|
||||
#else
|
||||
mcr p15, 0, r0, c7, c15, 0 @ Cache clean+invalidate
|
||||
#endif
|
||||
mcr p15, 0, r0, c7, c10, 4 @ drain write buffer
|
||||
mov pc, lr
|
||||
#endif
|
||||
|
||||
/*
|
||||
* dma_map_area(start, size, dir)
|
||||
* - start - kernel virtual start address
|
||||
|
|
|
|||
|
|
@ -273,10 +273,10 @@ do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
|
|||
local_irq_enable();
|
||||
|
||||
/*
|
||||
* If we're in an interrupt or have no user
|
||||
* If we're in an interrupt, or have no irqs, or have no user
|
||||
* context, we must not take the fault..
|
||||
*/
|
||||
if (faulthandler_disabled() || !mm)
|
||||
if (faulthandler_disabled() || irqs_disabled() || !mm)
|
||||
goto no_context;
|
||||
|
||||
if (user_mode(regs))
|
||||
|
|
|
|||
|
|
@ -173,8 +173,7 @@ unsigned long arch_mmap_rnd(void)
|
|||
{
|
||||
unsigned long rnd;
|
||||
|
||||
/* 8 bits of randomness in 20 address space bits */
|
||||
rnd = (unsigned long)get_random_int() % (1 << 8);
|
||||
rnd = (unsigned long)get_random_int() & ((1 << mmap_rnd_bits) - 1);
|
||||
|
||||
return rnd << PAGE_SHIFT;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ config ARM64
|
|||
select HAVE_ARCH_JUMP_LABEL
|
||||
select HAVE_ARCH_KASAN if SPARSEMEM_VMEMMAP && !(ARM64_16K_PAGES && ARM64_VA_BITS_48)
|
||||
select HAVE_ARCH_KGDB
|
||||
select HAVE_ARCH_MMAP_RND_BITS
|
||||
select HAVE_ARCH_MMAP_RND_COMPAT_BITS if COMPAT
|
||||
select HAVE_ARCH_SECCOMP_FILTER
|
||||
select HAVE_ARCH_TRACEHOOK
|
||||
select HAVE_BPF_JIT
|
||||
|
|
@ -104,9 +106,40 @@ config ARCH_PHYS_ADDR_T_64BIT
|
|||
config MMU
|
||||
def_bool y
|
||||
|
||||
config ARCH_MMAP_RND_BITS_MIN
|
||||
default 14 if ARM64_64K_PAGES
|
||||
default 16 if ARM64_16K_PAGES
|
||||
default 18
|
||||
|
||||
# max bits determined by the following formula:
|
||||
# VA_BITS - PAGE_SHIFT - 3
|
||||
config ARCH_MMAP_RND_BITS_MAX
|
||||
default 19 if ARM64_VA_BITS=36
|
||||
default 24 if ARM64_VA_BITS=39
|
||||
default 27 if ARM64_VA_BITS=42
|
||||
default 30 if ARM64_VA_BITS=47
|
||||
default 29 if ARM64_VA_BITS=48 && ARM64_64K_PAGES
|
||||
default 31 if ARM64_VA_BITS=48 && ARM64_16K_PAGES
|
||||
default 33 if ARM64_VA_BITS=48
|
||||
default 14 if ARM64_64K_PAGES
|
||||
default 16 if ARM64_16K_PAGES
|
||||
default 18
|
||||
|
||||
config ARCH_MMAP_RND_COMPAT_BITS_MIN
|
||||
default 7 if ARM64_64K_PAGES
|
||||
default 9 if ARM64_16K_PAGES
|
||||
default 11
|
||||
|
||||
config ARCH_MMAP_RND_COMPAT_BITS_MAX
|
||||
default 16
|
||||
|
||||
config NO_IOPORT_MAP
|
||||
def_bool y if !PCI
|
||||
|
||||
config ILLEGAL_POINTER_VALUE
|
||||
hex
|
||||
default 0xdead000000000000
|
||||
|
||||
config STACKTRACE_SUPPORT
|
||||
def_bool y
|
||||
|
||||
|
|
@ -716,6 +749,23 @@ config CMDLINE
|
|||
entering them here. As a minimum, you should specify the the
|
||||
root device (e.g. root=/dev/nfs).
|
||||
|
||||
choice
|
||||
prompt "Kernel command line type" if CMDLINE != ""
|
||||
default CMDLINE_FROM_BOOTLOADER
|
||||
|
||||
config CMDLINE_FROM_BOOTLOADER
|
||||
bool "Use bootloader kernel arguments if available"
|
||||
help
|
||||
Uses the command-line options passed by the boot loader. If
|
||||
the boot loader doesn't provide any, the default kernel command
|
||||
string provided in CMDLINE will be used.
|
||||
|
||||
config CMDLINE_EXTEND
|
||||
bool "Extend bootloader kernel arguments"
|
||||
help
|
||||
The command-line arguments provided by the boot loader will be
|
||||
appended to the default kernel command string.
|
||||
|
||||
config CMDLINE_FORCE
|
||||
bool "Always use the default kernel command string"
|
||||
help
|
||||
|
|
@ -723,6 +773,7 @@ config CMDLINE_FORCE
|
|||
loader passes other arguments to the kernel.
|
||||
This is useful if you cannot or don't want to change the
|
||||
command-line options your boot loader passes to the kernel.
|
||||
endchoice
|
||||
|
||||
config EFI_STUB
|
||||
bool
|
||||
|
|
@ -755,6 +806,21 @@ config DMI
|
|||
However, even with this option, the resultant kernel should
|
||||
continue to boot on existing non-UEFI platforms.
|
||||
|
||||
config BUILD_ARM64_APPENDED_DTB_IMAGE
|
||||
bool "Build a concatenated Image.gz/dtb by default"
|
||||
depends on OF
|
||||
help
|
||||
Enabling this option will cause a concatenated Image.gz and list of
|
||||
DTBs to be built by default (instead of a standalone Image.gz.)
|
||||
The image will built in arch/arm64/boot/Image.gz-dtb
|
||||
|
||||
config BUILD_ARM64_APPENDED_DTB_IMAGE_NAMES
|
||||
string "Default dtb names"
|
||||
depends on BUILD_ARM64_APPENDED_DTB_IMAGE
|
||||
help
|
||||
Space separated list of names of dtbs to append when
|
||||
building a concatenated Image.gz-dtb.
|
||||
|
||||
endmenu
|
||||
|
||||
menu "Userspace binary formats"
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ $(warning LSE atomics not supported by binutils)
|
|||
endif
|
||||
|
||||
KBUILD_CFLAGS += -mgeneral-regs-only $(lseinstr)
|
||||
KBUILD_CFLAGS += -fno-pic
|
||||
KBUILD_AFLAGS += $(lseinstr)
|
||||
|
||||
ifeq ($(CONFIG_CPU_BIG_ENDIAN), y)
|
||||
|
|
@ -73,7 +74,12 @@ libs-y := arch/arm64/lib/ $(libs-y)
|
|||
core-$(CONFIG_EFI_STUB) += $(objtree)/drivers/firmware/efi/libstub/lib.a
|
||||
|
||||
# Default target when executing plain make
|
||||
ifeq ($(CONFIG_BUILD_ARM64_APPENDED_DTB_IMAGE),y)
|
||||
KBUILD_IMAGE := Image.gz-dtb
|
||||
else
|
||||
KBUILD_IMAGE := Image.gz
|
||||
endif
|
||||
|
||||
KBUILD_DTBS := dtbs
|
||||
|
||||
all: $(KBUILD_IMAGE) $(KBUILD_DTBS)
|
||||
|
|
@ -100,6 +106,9 @@ dtbs: prepare scripts
|
|||
dtbs_install:
|
||||
$(Q)$(MAKE) $(dtbinst)=$(boot)/dts
|
||||
|
||||
Image.gz-dtb: vmlinux scripts dtbs
|
||||
$(Q)$(MAKE) $(build)=$(boot) $(boot)/$@
|
||||
|
||||
PHONY += vdso_install
|
||||
vdso_install:
|
||||
$(Q)$(MAKE) $(build)=arch/arm64/kernel/vdso $@
|
||||
|
|
|
|||
1
arch/arm64/boot/.gitignore
vendored
1
arch/arm64/boot/.gitignore
vendored
|
|
@ -1,2 +1,3 @@
|
|||
Image
|
||||
Image.gz
|
||||
Image.gz-dtb
|
||||
|
|
|
|||
|
|
@ -14,8 +14,18 @@
|
|||
# Based on the ia64 boot/Makefile.
|
||||
#
|
||||
|
||||
include $(srctree)/arch/arm64/boot/dts/Makefile
|
||||
|
||||
targets := Image Image.gz
|
||||
|
||||
DTB_NAMES := $(subst $\",,$(CONFIG_BUILD_ARM64_APPENDED_DTB_IMAGE_NAMES))
|
||||
ifneq ($(DTB_NAMES),)
|
||||
DTB_LIST := $(addsuffix .dtb,$(DTB_NAMES))
|
||||
else
|
||||
DTB_LIST := $(dtb-y)
|
||||
endif
|
||||
DTB_OBJS := $(addprefix $(obj)/dts/,$(DTB_LIST))
|
||||
|
||||
$(obj)/Image: vmlinux FORCE
|
||||
$(call if_changed,objcopy)
|
||||
|
||||
|
|
@ -34,6 +44,9 @@ $(obj)/Image.lzma: $(obj)/Image FORCE
|
|||
$(obj)/Image.lzo: $(obj)/Image FORCE
|
||||
$(call if_changed,lzo)
|
||||
|
||||
$(obj)/Image.gz-dtb: $(obj)/Image.gz $(DTB_OBJS) FORCE
|
||||
$(call if_changed,cat)
|
||||
|
||||
install: $(obj)/Image
|
||||
$(CONFIG_SHELL) $(srctree)/$(src)/install.sh $(KERNELRELEASE) \
|
||||
$(obj)/Image System.map "$(INSTALL_PATH)"
|
||||
|
|
|
|||
|
|
@ -21,3 +21,17 @@ dtstree := $(srctree)/$(src)
|
|||
dtb-$(CONFIG_OF_ALL_DTBS) := $(patsubst $(dtstree)/%.dts,%.dtb, $(foreach d,$(dts-dirs), $(wildcard $(dtstree)/$(d)/*.dts)))
|
||||
|
||||
always := $(dtb-y)
|
||||
|
||||
targets += dtbs
|
||||
|
||||
DTB_NAMES := $(subst $\",,$(CONFIG_BUILD_ARM64_APPENDED_DTB_IMAGE_NAMES))
|
||||
ifneq ($(DTB_NAMES),)
|
||||
DTB_LIST := $(addsuffix .dtb,$(DTB_NAMES))
|
||||
else
|
||||
DTB_LIST := $(dtb-y)
|
||||
endif
|
||||
targets += $(DTB_LIST)
|
||||
|
||||
dtbs: $(addprefix $(obj)/, $(DTB_LIST))
|
||||
|
||||
clean-files := dts/*.dtb *.dtb
|
||||
|
|
|
|||
|
|
@ -164,6 +164,70 @@ void machine_restart(char *cmd)
|
|||
while (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* dump a block of kernel memory from around the given address
|
||||
*/
|
||||
static void show_data(unsigned long addr, int nbytes, const char *name)
|
||||
{
|
||||
int i, j;
|
||||
int nlines;
|
||||
u32 *p;
|
||||
|
||||
/*
|
||||
* don't attempt to dump non-kernel addresses or
|
||||
* values that are probably just small negative numbers
|
||||
*/
|
||||
if (addr < PAGE_OFFSET || addr > -256UL)
|
||||
return;
|
||||
|
||||
printk("\n%s: %#lx:\n", name, addr);
|
||||
|
||||
/*
|
||||
* round address down to a 32 bit boundary
|
||||
* and always dump a multiple of 32 bytes
|
||||
*/
|
||||
p = (u32 *)(addr & ~(sizeof(u32) - 1));
|
||||
nbytes += (addr & (sizeof(u32) - 1));
|
||||
nlines = (nbytes + 31) / 32;
|
||||
|
||||
|
||||
for (i = 0; i < nlines; i++) {
|
||||
/*
|
||||
* just display low 16 bits of address to keep
|
||||
* each line of the dump < 80 characters
|
||||
*/
|
||||
printk("%04lx ", (unsigned long)p & 0xffff);
|
||||
for (j = 0; j < 8; j++) {
|
||||
u32 data;
|
||||
if (probe_kernel_address(p, data)) {
|
||||
printk(" ********");
|
||||
} else {
|
||||
printk(" %08x", data);
|
||||
}
|
||||
++p;
|
||||
}
|
||||
printk("\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void show_extra_register_data(struct pt_regs *regs, int nbytes)
|
||||
{
|
||||
mm_segment_t fs;
|
||||
unsigned int i;
|
||||
|
||||
fs = get_fs();
|
||||
set_fs(KERNEL_DS);
|
||||
show_data(regs->pc - nbytes, nbytes * 2, "PC");
|
||||
show_data(regs->regs[30] - nbytes, nbytes * 2, "LR");
|
||||
show_data(regs->sp - nbytes, nbytes * 2, "SP");
|
||||
for (i = 0; i < 30; i++) {
|
||||
char name[4];
|
||||
snprintf(name, sizeof(name), "X%u", i);
|
||||
show_data(regs->regs[i] - nbytes, nbytes * 2, name);
|
||||
}
|
||||
set_fs(fs);
|
||||
}
|
||||
|
||||
void __show_regs(struct pt_regs *regs)
|
||||
{
|
||||
int i, top_reg;
|
||||
|
|
@ -190,6 +254,8 @@ void __show_regs(struct pt_regs *regs)
|
|||
if (i % 2 == 0)
|
||||
printk("\n");
|
||||
}
|
||||
if (!user_mode(regs))
|
||||
show_extra_register_data(regs, 128);
|
||||
printk("\n");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ static void *__dma_alloc(struct device *dev, size_t size,
|
|||
/* create a coherent mapping */
|
||||
page = virt_to_page(ptr);
|
||||
coherent_ptr = dma_common_contiguous_remap(page, size, VM_USERMAP,
|
||||
prot, NULL);
|
||||
prot, __builtin_return_address(0));
|
||||
if (!coherent_ptr)
|
||||
goto no_map;
|
||||
|
||||
|
|
|
|||
|
|
@ -118,9 +118,11 @@ static void __init zone_sizes_init(unsigned long min, unsigned long max)
|
|||
}
|
||||
|
||||
#ifdef CONFIG_HAVE_ARCH_PFN_VALID
|
||||
#define PFN_MASK ((1UL << (64 - PAGE_SHIFT)) - 1)
|
||||
|
||||
int pfn_valid(unsigned long pfn)
|
||||
{
|
||||
return memblock_is_memory(pfn << PAGE_SHIFT);
|
||||
return (pfn & PFN_MASK) == pfn && memblock_is_memory(pfn << PAGE_SHIFT);
|
||||
}
|
||||
EXPORT_SYMBOL(pfn_valid);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -51,8 +51,12 @@ unsigned long arch_mmap_rnd(void)
|
|||
{
|
||||
unsigned long rnd;
|
||||
|
||||
rnd = (unsigned long)get_random_int() & STACK_RND_MASK;
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
if (test_thread_flag(TIF_32BIT))
|
||||
rnd = (unsigned long)get_random_int() & ((1 << mmap_rnd_compat_bits) - 1);
|
||||
else
|
||||
#endif
|
||||
rnd = (unsigned long)get_random_int() & ((1 << mmap_rnd_bits) - 1);
|
||||
return rnd << PAGE_SHIFT;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ config X86
|
|||
select ARCH_WANTS_DYNAMIC_TASK_STRUCT
|
||||
select ARCH_WANT_FRAME_POINTERS
|
||||
select ARCH_WANT_IPC_PARSE_VERSION if X86_32
|
||||
select HAVE_ARCH_MMAP_RND_BITS if MMU
|
||||
select HAVE_ARCH_MMAP_RND_COMPAT_BITS if MMU && COMPAT
|
||||
select ARCH_WANT_OPTIONAL_GPIOLIB
|
||||
select BUILDTIME_EXTABLE_SORT
|
||||
select CLKEVT_I8253
|
||||
|
|
@ -183,6 +185,20 @@ config HAVE_LATENCYTOP_SUPPORT
|
|||
config MMU
|
||||
def_bool y
|
||||
|
||||
config ARCH_MMAP_RND_BITS_MIN
|
||||
default 28 if 64BIT
|
||||
default 8
|
||||
|
||||
config ARCH_MMAP_RND_BITS_MAX
|
||||
default 32 if 64BIT
|
||||
default 16
|
||||
|
||||
config ARCH_MMAP_RND_COMPAT_BITS_MIN
|
||||
default 8
|
||||
|
||||
config ARCH_MMAP_RND_COMPAT_BITS_MAX
|
||||
default 16
|
||||
|
||||
config SBUS
|
||||
bool
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,6 @@
|
|||
#ifndef _ASM_X86_IDLE_H
|
||||
#define _ASM_X86_IDLE_H
|
||||
|
||||
#define IDLE_START 1
|
||||
#define IDLE_END 2
|
||||
|
||||
struct notifier_block;
|
||||
void idle_notifier_register(struct notifier_block *n);
|
||||
void idle_notifier_unregister(struct notifier_block *n);
|
||||
|
||||
#ifdef CONFIG_X86_64
|
||||
void enter_idle(void);
|
||||
void exit_idle(void);
|
||||
|
|
|
|||
|
|
@ -62,19 +62,6 @@ EXPORT_PER_CPU_SYMBOL(cpu_tss);
|
|||
|
||||
#ifdef CONFIG_X86_64
|
||||
static DEFINE_PER_CPU(unsigned char, is_idle);
|
||||
static ATOMIC_NOTIFIER_HEAD(idle_notifier);
|
||||
|
||||
void idle_notifier_register(struct notifier_block *n)
|
||||
{
|
||||
atomic_notifier_chain_register(&idle_notifier, n);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(idle_notifier_register);
|
||||
|
||||
void idle_notifier_unregister(struct notifier_block *n)
|
||||
{
|
||||
atomic_notifier_chain_unregister(&idle_notifier, n);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(idle_notifier_unregister);
|
||||
#endif
|
||||
|
||||
/*
|
||||
|
|
@ -251,14 +238,14 @@ static inline void play_dead(void)
|
|||
void enter_idle(void)
|
||||
{
|
||||
this_cpu_write(is_idle, 1);
|
||||
atomic_notifier_call_chain(&idle_notifier, IDLE_START, NULL);
|
||||
idle_notifier_call_chain(IDLE_START);
|
||||
}
|
||||
|
||||
static void __exit_idle(void)
|
||||
{
|
||||
if (x86_test_and_clear_bit_percpu(0, is_idle) == 0)
|
||||
return;
|
||||
atomic_notifier_call_chain(&idle_notifier, IDLE_END, NULL);
|
||||
idle_notifier_call_chain(IDLE_END);
|
||||
}
|
||||
|
||||
/* Called from interrupts to signify idle end */
|
||||
|
|
|
|||
|
|
@ -69,14 +69,14 @@ unsigned long arch_mmap_rnd(void)
|
|||
{
|
||||
unsigned long rnd;
|
||||
|
||||
/*
|
||||
* 8 bits of randomness in 32bit mmaps, 20 address space bits
|
||||
* 28 bits of randomness in 64bit mmaps, 40 address space bits
|
||||
*/
|
||||
if (mmap_is_ia32())
|
||||
rnd = (unsigned long)get_random_int() % (1<<8);
|
||||
#ifdef CONFIG_COMPAT
|
||||
rnd = (unsigned long)get_random_int() & ((1 << mmap_rnd_compat_bits) - 1);
|
||||
#else
|
||||
rnd = (unsigned long)get_random_int() & ((1 << mmap_rnd_bits) - 1);
|
||||
#endif
|
||||
else
|
||||
rnd = (unsigned long)get_random_int() % (1<<28);
|
||||
rnd = (unsigned long)get_random_int() & ((1 << mmap_rnd_bits) - 1);
|
||||
|
||||
return rnd << PAGE_SHIFT;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1117,6 +1117,22 @@ static void disk_release(struct device *dev)
|
|||
blk_put_queue(disk->queue);
|
||||
kfree(disk);
|
||||
}
|
||||
|
||||
static int disk_uevent(struct device *dev, struct kobj_uevent_env *env)
|
||||
{
|
||||
struct gendisk *disk = dev_to_disk(dev);
|
||||
struct disk_part_iter piter;
|
||||
struct hd_struct *part;
|
||||
int cnt = 0;
|
||||
|
||||
disk_part_iter_init(&piter, disk, 0);
|
||||
while((part = disk_part_iter_next(&piter)))
|
||||
cnt++;
|
||||
disk_part_iter_exit(&piter);
|
||||
add_uevent_var(env, "NPARTS=%u", cnt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct class block_class = {
|
||||
.name = "block",
|
||||
};
|
||||
|
|
@ -1136,6 +1152,7 @@ static struct device_type disk_type = {
|
|||
.groups = disk_attr_groups,
|
||||
.release = disk_release,
|
||||
.devnode = block_devnode,
|
||||
.uevent = disk_uevent,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
|
|
|
|||
|
|
@ -216,10 +216,21 @@ static void part_release(struct device *dev)
|
|||
kfree(p);
|
||||
}
|
||||
|
||||
static int part_uevent(struct device *dev, struct kobj_uevent_env *env)
|
||||
{
|
||||
struct hd_struct *part = dev_to_part(dev);
|
||||
|
||||
add_uevent_var(env, "PARTN=%u", part->partno);
|
||||
if (part->info && part->info->volname[0])
|
||||
add_uevent_var(env, "PARTNAME=%s", part->info->volname);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct device_type part_type = {
|
||||
.name = "partition",
|
||||
.groups = part_attr_groups,
|
||||
.release = part_release,
|
||||
.uevent = part_uevent,
|
||||
};
|
||||
|
||||
static void delete_partition_rcu_cb(struct rcu_head *head)
|
||||
|
|
|
|||
|
|
@ -104,6 +104,8 @@ source "drivers/memstick/Kconfig"
|
|||
|
||||
source "drivers/leds/Kconfig"
|
||||
|
||||
source "drivers/switch/Kconfig"
|
||||
|
||||
source "drivers/accessibility/Kconfig"
|
||||
|
||||
source "drivers/infiniband/Kconfig"
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ obj-$(CONFIG_CPU_IDLE) += cpuidle/
|
|||
obj-y += mmc/
|
||||
obj-$(CONFIG_MEMSTICK) += memstick/
|
||||
obj-y += leds/
|
||||
obj-$(CONFIG_SWITCH) += switch/
|
||||
obj-$(CONFIG_INFINIBAND) += infiniband/
|
||||
obj-$(CONFIG_SGI_SN) += sn/
|
||||
obj-y += firmware/
|
||||
|
|
|
|||
|
|
@ -1321,6 +1321,7 @@ static void binder_transaction(struct binder_proc *proc,
|
|||
struct binder_transaction *t;
|
||||
struct binder_work *tcomplete;
|
||||
binder_size_t *offp, *off_end;
|
||||
binder_size_t off_min;
|
||||
struct binder_proc *target_proc;
|
||||
struct binder_thread *target_thread = NULL;
|
||||
struct binder_node *target_node = NULL;
|
||||
|
|
@ -1522,18 +1523,24 @@ static void binder_transaction(struct binder_proc *proc,
|
|||
goto err_bad_offset;
|
||||
}
|
||||
off_end = (void *)offp + tr->offsets_size;
|
||||
off_min = 0;
|
||||
for (; offp < off_end; offp++) {
|
||||
struct flat_binder_object *fp;
|
||||
|
||||
if (*offp > t->buffer->data_size - sizeof(*fp) ||
|
||||
*offp < off_min ||
|
||||
t->buffer->data_size < sizeof(*fp) ||
|
||||
!IS_ALIGNED(*offp, sizeof(u32))) {
|
||||
binder_user_error("%d:%d got transaction with invalid offset, %lld\n",
|
||||
proc->pid, thread->pid, (u64)*offp);
|
||||
binder_user_error("%d:%d got transaction with invalid offset, %lld (min %lld, max %lld)\n",
|
||||
proc->pid, thread->pid, (u64)*offp,
|
||||
(u64)off_min,
|
||||
(u64)(t->buffer->data_size -
|
||||
sizeof(*fp)));
|
||||
return_error = BR_FAILED_REPLY;
|
||||
goto err_bad_offset;
|
||||
}
|
||||
fp = (struct flat_binder_object *)(t->buffer->data + *offp);
|
||||
off_min = *offp + sizeof(struct flat_binder_object);
|
||||
switch (fp->type) {
|
||||
case BINDER_TYPE_BINDER:
|
||||
case BINDER_TYPE_WEAK_BINDER: {
|
||||
|
|
@ -3593,13 +3600,24 @@ static int binder_transactions_show(struct seq_file *m, void *unused)
|
|||
|
||||
static int binder_proc_show(struct seq_file *m, void *unused)
|
||||
{
|
||||
struct binder_proc *itr;
|
||||
struct binder_proc *proc = m->private;
|
||||
int do_lock = !binder_debug_no_lock;
|
||||
bool valid_proc = false;
|
||||
|
||||
if (do_lock)
|
||||
binder_lock(__func__);
|
||||
seq_puts(m, "binder proc state:\n");
|
||||
print_binder_proc(m, proc, 1);
|
||||
|
||||
hlist_for_each_entry(itr, &binder_procs, proc_node) {
|
||||
if (itr == proc) {
|
||||
valid_proc = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (valid_proc) {
|
||||
seq_puts(m, "binder proc state:\n");
|
||||
print_binder_proc(m, proc, 1);
|
||||
}
|
||||
if (do_lock)
|
||||
binder_unlock(__func__);
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
#include <linux/cpufreq.h>
|
||||
#include <linux/cpuidle.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/wakeup_reason.h>
|
||||
|
||||
#include "../base.h"
|
||||
#include "power.h"
|
||||
|
|
@ -59,6 +60,12 @@ struct suspend_stats suspend_stats;
|
|||
static DEFINE_MUTEX(dpm_list_mtx);
|
||||
static pm_message_t pm_transition;
|
||||
|
||||
static void dpm_drv_timeout(unsigned long data);
|
||||
struct dpm_drv_wd_data {
|
||||
struct device *dev;
|
||||
struct task_struct *tsk;
|
||||
};
|
||||
|
||||
static int async_error;
|
||||
|
||||
static char *pm_verb(int event)
|
||||
|
|
@ -829,6 +836,30 @@ static void async_resume(void *data, async_cookie_t cookie)
|
|||
put_device(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* dpm_drv_timeout - Driver suspend / resume watchdog handler
|
||||
* @data: struct device which timed out
|
||||
*
|
||||
* Called when a driver has timed out suspending or resuming.
|
||||
* There's not much we can do here to recover so
|
||||
* BUG() out for a crash-dump
|
||||
*
|
||||
*/
|
||||
static void dpm_drv_timeout(unsigned long data)
|
||||
{
|
||||
struct dpm_drv_wd_data *wd_data = (void *)data;
|
||||
struct device *dev = wd_data->dev;
|
||||
struct task_struct *tsk = wd_data->tsk;
|
||||
|
||||
printk(KERN_EMERG "**** DPM device timeout: %s (%s)\n", dev_name(dev),
|
||||
(dev->driver ? dev->driver->name : "no driver"));
|
||||
|
||||
printk(KERN_EMERG "dpm suspend stack:\n");
|
||||
show_stack(tsk, NULL);
|
||||
|
||||
BUG();
|
||||
}
|
||||
|
||||
/**
|
||||
* dpm_resume - Execute "resume" callbacks for non-sysdev devices.
|
||||
* @state: PM transition of the system being carried out.
|
||||
|
|
@ -1347,6 +1378,9 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
|
|||
pm_callback_t callback = NULL;
|
||||
char *info = NULL;
|
||||
int error = 0;
|
||||
struct timer_list timer;
|
||||
struct dpm_drv_wd_data data;
|
||||
char suspend_abort[MAX_SUSPEND_ABORT_LEN];
|
||||
DECLARE_DPM_WATCHDOG_ON_STACK(wd);
|
||||
|
||||
TRACE_DEVICE(dev);
|
||||
|
|
@ -1367,12 +1401,23 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
|
|||
pm_wakeup_event(dev, 0);
|
||||
|
||||
if (pm_wakeup_pending()) {
|
||||
pm_get_active_wakeup_sources(suspend_abort,
|
||||
MAX_SUSPEND_ABORT_LEN);
|
||||
log_suspend_abort_reason(suspend_abort);
|
||||
async_error = -EBUSY;
|
||||
goto Complete;
|
||||
}
|
||||
|
||||
if (dev->power.syscore)
|
||||
goto Complete;
|
||||
|
||||
data.dev = dev;
|
||||
data.tsk = get_current();
|
||||
init_timer_on_stack(&timer);
|
||||
timer.expires = jiffies + HZ * 12;
|
||||
timer.function = dpm_drv_timeout;
|
||||
timer.data = (unsigned long)&data;
|
||||
add_timer(&timer);
|
||||
|
||||
if (dev->power.direct_complete) {
|
||||
if (pm_runtime_status_suspended(dev)) {
|
||||
|
|
@ -1453,6 +1498,9 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
|
|||
device_unlock(dev);
|
||||
dpm_watchdog_clear(&wd);
|
||||
|
||||
del_timer_sync(&timer);
|
||||
destroy_timer_on_stack(&timer);
|
||||
|
||||
Complete:
|
||||
complete_all(&dev->power.completion);
|
||||
if (error)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <linux/seq_file.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/pm_wakeirq.h>
|
||||
#include <linux/types.h>
|
||||
#include <trace/events/power.h>
|
||||
|
||||
#include "power.h"
|
||||
|
|
@ -804,6 +805,37 @@ void pm_wakeup_event(struct device *dev, unsigned int msec)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(pm_wakeup_event);
|
||||
|
||||
void pm_get_active_wakeup_sources(char *pending_wakeup_source, size_t max)
|
||||
{
|
||||
struct wakeup_source *ws, *last_active_ws = NULL;
|
||||
int len = 0;
|
||||
bool active = false;
|
||||
|
||||
rcu_read_lock();
|
||||
list_for_each_entry_rcu(ws, &wakeup_sources, entry) {
|
||||
if (ws->active && len < max) {
|
||||
if (!active)
|
||||
len += scnprintf(pending_wakeup_source, max,
|
||||
"Pending Wakeup Sources: ");
|
||||
len += scnprintf(pending_wakeup_source + len, max - len,
|
||||
"%s ", ws->name);
|
||||
active = true;
|
||||
} else if (!active &&
|
||||
(!last_active_ws ||
|
||||
ktime_to_ns(ws->last_time) >
|
||||
ktime_to_ns(last_active_ws->last_time))) {
|
||||
last_active_ws = ws;
|
||||
}
|
||||
}
|
||||
if (!active && last_active_ws) {
|
||||
scnprintf(pending_wakeup_source, max,
|
||||
"Last active Wakeup Source: %s",
|
||||
last_active_ws->name);
|
||||
}
|
||||
rcu_read_unlock();
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_get_active_wakeup_sources);
|
||||
|
||||
void pm_print_active_wakeup_sources(void)
|
||||
{
|
||||
struct wakeup_source *ws;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <trace/events/power.h>
|
||||
#include <linux/wakeup_reason.h>
|
||||
|
||||
static LIST_HEAD(syscore_ops_list);
|
||||
static DEFINE_MUTEX(syscore_ops_lock);
|
||||
|
|
@ -75,6 +76,8 @@ int syscore_suspend(void)
|
|||
return 0;
|
||||
|
||||
err_out:
|
||||
log_suspend_abort_reason("System core suspend callback %pF failed",
|
||||
ops->suspend);
|
||||
pr_err("PM: System core suspend callback %pF failed.\n", ops->suspend);
|
||||
|
||||
list_for_each_entry_continue(ops, &syscore_ops_list, node)
|
||||
|
|
|
|||
|
|
@ -588,6 +588,10 @@ config DEVPORT
|
|||
depends on ISA || PCI
|
||||
default y
|
||||
|
||||
config DCC_TTY
|
||||
tristate "DCC tty driver"
|
||||
depends on ARM
|
||||
|
||||
source "drivers/s390/char/Kconfig"
|
||||
|
||||
config TILE_SROM
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ obj-$(CONFIG_PCMCIA) += pcmcia/
|
|||
obj-$(CONFIG_HANGCHECK_TIMER) += hangcheck-timer.o
|
||||
obj-$(CONFIG_TCG_TPM) += tpm/
|
||||
|
||||
obj-$(CONFIG_DCC_TTY) += dcc_tty.o
|
||||
obj-$(CONFIG_PS3_FLASH) += ps3flash.o
|
||||
|
||||
obj-$(CONFIG_JS_RTC) += js-rtc.o
|
||||
|
|
|
|||
326
drivers/char/dcc_tty.c
Normal file
326
drivers/char/dcc_tty.c
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
/* drivers/char/dcc_tty.c
|
||||
*
|
||||
* Copyright (C) 2007 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/console.h>
|
||||
#include <linux/hrtimer.h>
|
||||
#include <linux/tty.h>
|
||||
#include <linux/tty_driver.h>
|
||||
#include <linux/tty_flip.h>
|
||||
|
||||
MODULE_DESCRIPTION("DCC TTY Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION("1.0");
|
||||
|
||||
DEFINE_SPINLOCK(g_dcc_tty_lock);
|
||||
static struct hrtimer g_dcc_timer;
|
||||
static char g_dcc_buffer[16];
|
||||
static int g_dcc_buffer_head;
|
||||
static int g_dcc_buffer_count;
|
||||
static unsigned g_dcc_write_delay_usecs = 1;
|
||||
static struct tty_driver *g_dcc_tty_driver;
|
||||
static struct tty_struct *g_dcc_tty;
|
||||
static int g_dcc_tty_open_count;
|
||||
|
||||
static void dcc_poll_locked(void)
|
||||
{
|
||||
char ch;
|
||||
int rch;
|
||||
int written;
|
||||
|
||||
while (g_dcc_buffer_count) {
|
||||
ch = g_dcc_buffer[g_dcc_buffer_head];
|
||||
asm(
|
||||
"mrc 14, 0, r15, c0, c1, 0\n"
|
||||
"mcrcc 14, 0, %1, c0, c5, 0\n"
|
||||
"movcc %0, #1\n"
|
||||
"movcs %0, #0\n"
|
||||
: "=r" (written)
|
||||
: "r" (ch)
|
||||
);
|
||||
if (written) {
|
||||
if (ch == '\n')
|
||||
g_dcc_buffer[g_dcc_buffer_head] = '\r';
|
||||
else {
|
||||
g_dcc_buffer_head = (g_dcc_buffer_head + 1) % ARRAY_SIZE(g_dcc_buffer);
|
||||
g_dcc_buffer_count--;
|
||||
if (g_dcc_tty)
|
||||
tty_wakeup(g_dcc_tty);
|
||||
}
|
||||
g_dcc_write_delay_usecs = 1;
|
||||
} else {
|
||||
if (g_dcc_write_delay_usecs > 0x100)
|
||||
break;
|
||||
g_dcc_write_delay_usecs <<= 1;
|
||||
udelay(g_dcc_write_delay_usecs);
|
||||
}
|
||||
}
|
||||
|
||||
if (g_dcc_tty && !test_bit(TTY_THROTTLED, &g_dcc_tty->flags)) {
|
||||
asm(
|
||||
"mrc 14, 0, %0, c0, c1, 0\n"
|
||||
"tst %0, #(1 << 30)\n"
|
||||
"moveq %0, #-1\n"
|
||||
"mrcne 14, 0, %0, c0, c5, 0\n"
|
||||
: "=r" (rch)
|
||||
);
|
||||
if (rch >= 0) {
|
||||
ch = rch;
|
||||
tty_insert_flip_string(g_dcc_tty->port, &ch, 1);
|
||||
tty_flip_buffer_push(g_dcc_tty->port);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (g_dcc_buffer_count)
|
||||
hrtimer_start(&g_dcc_timer, ktime_set(0, g_dcc_write_delay_usecs * NSEC_PER_USEC), HRTIMER_MODE_REL);
|
||||
else
|
||||
hrtimer_start(&g_dcc_timer, ktime_set(0, 20 * NSEC_PER_MSEC), HRTIMER_MODE_REL);
|
||||
}
|
||||
|
||||
static int dcc_tty_open(struct tty_struct * tty, struct file * filp)
|
||||
{
|
||||
int ret;
|
||||
unsigned long irq_flags;
|
||||
|
||||
spin_lock_irqsave(&g_dcc_tty_lock, irq_flags);
|
||||
if (g_dcc_tty == NULL || g_dcc_tty == tty) {
|
||||
g_dcc_tty = tty;
|
||||
g_dcc_tty_open_count++;
|
||||
ret = 0;
|
||||
} else
|
||||
ret = -EBUSY;
|
||||
spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags);
|
||||
|
||||
printk("dcc_tty_open, tty %p, f_flags %x, returned %d\n", tty, filp->f_flags, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void dcc_tty_close(struct tty_struct * tty, struct file * filp)
|
||||
{
|
||||
printk("dcc_tty_close, tty %p, f_flags %x\n", tty, filp->f_flags);
|
||||
if (g_dcc_tty == tty) {
|
||||
if (--g_dcc_tty_open_count == 0)
|
||||
g_dcc_tty = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int dcc_write(const unsigned char *buf_start, int count)
|
||||
{
|
||||
const unsigned char *buf = buf_start;
|
||||
unsigned long irq_flags;
|
||||
int copy_len;
|
||||
int space_left;
|
||||
int tail;
|
||||
|
||||
if (count < 1)
|
||||
return 0;
|
||||
|
||||
spin_lock_irqsave(&g_dcc_tty_lock, irq_flags);
|
||||
do {
|
||||
tail = (g_dcc_buffer_head + g_dcc_buffer_count) % ARRAY_SIZE(g_dcc_buffer);
|
||||
copy_len = ARRAY_SIZE(g_dcc_buffer) - tail;
|
||||
space_left = ARRAY_SIZE(g_dcc_buffer) - g_dcc_buffer_count;
|
||||
if (copy_len > space_left)
|
||||
copy_len = space_left;
|
||||
if (copy_len > count)
|
||||
copy_len = count;
|
||||
memcpy(&g_dcc_buffer[tail], buf, copy_len);
|
||||
g_dcc_buffer_count += copy_len;
|
||||
buf += copy_len;
|
||||
count -= copy_len;
|
||||
if (copy_len < count && copy_len < space_left) {
|
||||
space_left -= copy_len;
|
||||
copy_len = count;
|
||||
if (copy_len > space_left) {
|
||||
copy_len = space_left;
|
||||
}
|
||||
memcpy(g_dcc_buffer, buf, copy_len);
|
||||
buf += copy_len;
|
||||
count -= copy_len;
|
||||
g_dcc_buffer_count += copy_len;
|
||||
}
|
||||
dcc_poll_locked();
|
||||
space_left = ARRAY_SIZE(g_dcc_buffer) - g_dcc_buffer_count;
|
||||
} while(count && space_left);
|
||||
spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags);
|
||||
return buf - buf_start;
|
||||
}
|
||||
|
||||
static int dcc_tty_write(struct tty_struct * tty, const unsigned char *buf, int count)
|
||||
{
|
||||
int ret;
|
||||
/* printk("dcc_tty_write %p, %d\n", buf, count); */
|
||||
ret = dcc_write(buf, count);
|
||||
if (ret != count)
|
||||
printk("dcc_tty_write %p, %d, returned %d\n", buf, count, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dcc_tty_write_room(struct tty_struct *tty)
|
||||
{
|
||||
int space_left;
|
||||
unsigned long irq_flags;
|
||||
|
||||
spin_lock_irqsave(&g_dcc_tty_lock, irq_flags);
|
||||
space_left = ARRAY_SIZE(g_dcc_buffer) - g_dcc_buffer_count;
|
||||
spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags);
|
||||
return space_left;
|
||||
}
|
||||
|
||||
static int dcc_tty_chars_in_buffer(struct tty_struct *tty)
|
||||
{
|
||||
int ret;
|
||||
asm(
|
||||
"mrc 14, 0, %0, c0, c1, 0\n"
|
||||
"mov %0, %0, LSR #30\n"
|
||||
"and %0, %0, #1\n"
|
||||
: "=r" (ret)
|
||||
);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void dcc_tty_unthrottle(struct tty_struct * tty)
|
||||
{
|
||||
unsigned long irq_flags;
|
||||
|
||||
spin_lock_irqsave(&g_dcc_tty_lock, irq_flags);
|
||||
dcc_poll_locked();
|
||||
spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags);
|
||||
}
|
||||
|
||||
static enum hrtimer_restart dcc_tty_timer_func(struct hrtimer *timer)
|
||||
{
|
||||
unsigned long irq_flags;
|
||||
|
||||
spin_lock_irqsave(&g_dcc_tty_lock, irq_flags);
|
||||
dcc_poll_locked();
|
||||
spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags);
|
||||
return HRTIMER_NORESTART;
|
||||
}
|
||||
|
||||
void dcc_console_write(struct console *co, const char *b, unsigned count)
|
||||
{
|
||||
#if 1
|
||||
dcc_write(b, count);
|
||||
#else
|
||||
/* blocking printk */
|
||||
while (count > 0) {
|
||||
int written;
|
||||
written = dcc_write(b, count);
|
||||
if (written) {
|
||||
b += written;
|
||||
count -= written;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static struct tty_driver *dcc_console_device(struct console *c, int *index)
|
||||
{
|
||||
*index = 0;
|
||||
return g_dcc_tty_driver;
|
||||
}
|
||||
|
||||
static int __init dcc_console_setup(struct console *co, char *options)
|
||||
{
|
||||
if (co->index != 0)
|
||||
return -ENODEV;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static struct console dcc_console =
|
||||
{
|
||||
.name = "ttyDCC",
|
||||
.write = dcc_console_write,
|
||||
.device = dcc_console_device,
|
||||
.setup = dcc_console_setup,
|
||||
.flags = CON_PRINTBUFFER,
|
||||
.index = -1,
|
||||
};
|
||||
|
||||
static struct tty_operations dcc_tty_ops = {
|
||||
.open = dcc_tty_open,
|
||||
.close = dcc_tty_close,
|
||||
.write = dcc_tty_write,
|
||||
.write_room = dcc_tty_write_room,
|
||||
.chars_in_buffer = dcc_tty_chars_in_buffer,
|
||||
.unthrottle = dcc_tty_unthrottle,
|
||||
};
|
||||
|
||||
static int __init dcc_tty_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
hrtimer_init(&g_dcc_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||
g_dcc_timer.function = dcc_tty_timer_func;
|
||||
|
||||
g_dcc_tty_driver = alloc_tty_driver(1);
|
||||
if (!g_dcc_tty_driver) {
|
||||
printk(KERN_ERR "dcc_tty_probe: alloc_tty_driver failed\n");
|
||||
ret = -ENOMEM;
|
||||
goto err_alloc_tty_driver_failed;
|
||||
}
|
||||
g_dcc_tty_driver->owner = THIS_MODULE;
|
||||
g_dcc_tty_driver->driver_name = "dcc";
|
||||
g_dcc_tty_driver->name = "ttyDCC";
|
||||
g_dcc_tty_driver->major = 0; // auto assign
|
||||
g_dcc_tty_driver->minor_start = 0;
|
||||
g_dcc_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
|
||||
g_dcc_tty_driver->subtype = SERIAL_TYPE_NORMAL;
|
||||
g_dcc_tty_driver->init_termios = tty_std_termios;
|
||||
g_dcc_tty_driver->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
|
||||
tty_set_operations(g_dcc_tty_driver, &dcc_tty_ops);
|
||||
ret = tty_register_driver(g_dcc_tty_driver);
|
||||
if (ret) {
|
||||
printk(KERN_ERR "dcc_tty_probe: tty_register_driver failed, %d\n", ret);
|
||||
goto err_tty_register_driver_failed;
|
||||
}
|
||||
tty_register_device(g_dcc_tty_driver, 0, NULL);
|
||||
|
||||
register_console(&dcc_console);
|
||||
hrtimer_start(&g_dcc_timer, ktime_set(0, 0), HRTIMER_MODE_REL);
|
||||
|
||||
return 0;
|
||||
|
||||
err_tty_register_driver_failed:
|
||||
put_tty_driver(g_dcc_tty_driver);
|
||||
g_dcc_tty_driver = NULL;
|
||||
err_alloc_tty_driver_failed:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit dcc_tty_exit(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
tty_unregister_device(g_dcc_tty_driver, 0);
|
||||
ret = tty_unregister_driver(g_dcc_tty_driver);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "dcc_tty_remove: tty_unregister_driver failed, %d\n", ret);
|
||||
} else {
|
||||
put_tty_driver(g_dcc_tty_driver);
|
||||
}
|
||||
g_dcc_tty_driver = NULL;
|
||||
}
|
||||
|
||||
module_init(dcc_tty_init);
|
||||
module_exit(dcc_tty_exit);
|
||||
|
||||
|
||||
|
|
@ -102,6 +102,16 @@ config CPU_FREQ_DEFAULT_GOV_CONSERVATIVE
|
|||
Be aware that not all cpufreq drivers support the conservative
|
||||
governor. If unsure have a look at the help section of the
|
||||
driver. Fallback governor will be the performance governor.
|
||||
|
||||
config CPU_FREQ_DEFAULT_GOV_INTERACTIVE
|
||||
bool "interactive"
|
||||
select CPU_FREQ_GOV_INTERACTIVE
|
||||
help
|
||||
Use the CPUFreq governor 'interactive' as default. This allows
|
||||
you to get a full dynamic cpu frequency capable system by simply
|
||||
loading your cpufreq low-level hardware driver, using the
|
||||
'interactive' governor for latency-sensitive workloads.
|
||||
|
||||
endchoice
|
||||
|
||||
config CPU_FREQ_GOV_PERFORMANCE
|
||||
|
|
@ -159,6 +169,20 @@ config CPU_FREQ_GOV_ONDEMAND
|
|||
|
||||
If in doubt, say N.
|
||||
|
||||
config CPU_FREQ_GOV_INTERACTIVE
|
||||
bool "'interactive' cpufreq policy governor"
|
||||
help
|
||||
'interactive' - This driver adds a dynamic cpufreq policy governor
|
||||
designed for latency-sensitive workloads.
|
||||
|
||||
This governor attempts to reduce the latency of clock
|
||||
increases so that the system is more responsive to
|
||||
interactive workloads.
|
||||
|
||||
For details, take a look at linux/Documentation/cpu-freq.
|
||||
|
||||
If in doubt, say N.
|
||||
|
||||
config CPU_FREQ_GOV_CONSERVATIVE
|
||||
tristate "'conservative' cpufreq governor"
|
||||
depends on CPU_FREQ
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ obj-$(CONFIG_CPU_FREQ_GOV_POWERSAVE) += cpufreq_powersave.o
|
|||
obj-$(CONFIG_CPU_FREQ_GOV_USERSPACE) += cpufreq_userspace.o
|
||||
obj-$(CONFIG_CPU_FREQ_GOV_ONDEMAND) += cpufreq_ondemand.o
|
||||
obj-$(CONFIG_CPU_FREQ_GOV_CONSERVATIVE) += cpufreq_conservative.o
|
||||
obj-$(CONFIG_CPU_FREQ_GOV_INTERACTIVE) += cpufreq_interactive.o
|
||||
obj-$(CONFIG_CPU_FREQ_GOV_COMMON) += cpufreq_governor.o
|
||||
|
||||
obj-$(CONFIG_CPUFREQ_DT) += cpufreq-dt.o
|
||||
|
|
|
|||
|
|
@ -2128,6 +2128,7 @@ static int cpufreq_set_policy(struct cpufreq_policy *policy,
|
|||
|
||||
policy->min = new_policy->min;
|
||||
policy->max = new_policy->max;
|
||||
trace_cpu_frequency_limits(policy->max, policy->min, policy->cpu);
|
||||
|
||||
pr_debug("new min and max freqs are %u - %u kHz\n",
|
||||
policy->min, policy->max);
|
||||
|
|
|
|||
1360
drivers/cpufreq/cpufreq_interactive.c
Normal file
1360
drivers/cpufreq/cpufreq_interactive.c
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -178,7 +178,12 @@ static inline int performance_multiplier(unsigned long nr_iowaiters, unsigned lo
|
|||
|
||||
/* for higher loadavg, we are more reluctant */
|
||||
|
||||
mult += 2 * get_loadavg(load);
|
||||
/*
|
||||
* this doesn't work as intended - it is almost always 0, but can
|
||||
* sometimes, depending on workload, spike very high into the hundreds
|
||||
* even when the average cpu load is under 10%.
|
||||
*/
|
||||
/* mult += 2 * get_loadavg(); */
|
||||
|
||||
/* for IO wait tasks (per cpu!) we add 5x each */
|
||||
mult += 10 * nr_iowaiters;
|
||||
|
|
|
|||
|
|
@ -304,8 +304,12 @@ fence_remove_callback(struct fence *fence, struct fence_cb *cb)
|
|||
spin_lock_irqsave(fence->lock, flags);
|
||||
|
||||
ret = !list_empty(&cb->node);
|
||||
if (ret)
|
||||
if (ret) {
|
||||
list_del_init(&cb->node);
|
||||
if (list_empty(&fence->cb_list))
|
||||
if (fence->ops->disable_signaling)
|
||||
fence->ops->disable_signaling(fence);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(fence->lock, flags);
|
||||
|
||||
|
|
|
|||
|
|
@ -434,6 +434,16 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
|
|||
if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON)
|
||||
td->buttons_count++;
|
||||
|
||||
/* Only map fields from TouchScreen or TouchPad collections.
|
||||
* We need to ignore fields that belong to other collections
|
||||
* such as Mouse that might have the same GenericDesktop usages. */
|
||||
if (field->application == HID_DG_TOUCHSCREEN)
|
||||
set_bit(INPUT_PROP_DIRECT, hi->input->propbit);
|
||||
else if (field->application == HID_DG_TOUCHPAD)
|
||||
set_bit(INPUT_PROP_POINTER, hi->input->propbit);
|
||||
else
|
||||
return 0;
|
||||
|
||||
if (usage->usage_index)
|
||||
prev_usage = &field->usage[usage->usage_index - 1];
|
||||
|
||||
|
|
|
|||
|
|
@ -253,6 +253,11 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
|
|||
goto err_free;
|
||||
}
|
||||
|
||||
if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 0, 0, 16)) {
|
||||
ret = -ENODEV;
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
|
|
|
|||
|
|
@ -187,6 +187,19 @@ config INPUT_APMPOWER
|
|||
To compile this driver as a module, choose M here: the
|
||||
module will be called apm-power.
|
||||
|
||||
config INPUT_KEYRESET
|
||||
bool "Reset key"
|
||||
depends on INPUT
|
||||
select INPUT_KEYCOMBO
|
||||
---help---
|
||||
Say Y here if you want to reboot when some keys are pressed;
|
||||
|
||||
config INPUT_KEYCOMBO
|
||||
bool "Key combo"
|
||||
depends on INPUT
|
||||
---help---
|
||||
Say Y here if you want to take action when some keys are pressed;
|
||||
|
||||
comment "Input Device Drivers"
|
||||
|
||||
source "drivers/input/keyboard/Kconfig"
|
||||
|
|
|
|||
|
|
@ -26,3 +26,6 @@ obj-$(CONFIG_INPUT_TOUCHSCREEN) += touchscreen/
|
|||
obj-$(CONFIG_INPUT_MISC) += misc/
|
||||
|
||||
obj-$(CONFIG_INPUT_APMPOWER) += apm-power.o
|
||||
obj-$(CONFIG_INPUT_KEYRESET) += keyreset.o
|
||||
obj-$(CONFIG_INPUT_KEYCOMBO) += keycombo.o
|
||||
|
||||
|
|
|
|||
261
drivers/input/keycombo.c
Normal file
261
drivers/input/keycombo.c
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
/* drivers/input/keycombo.c
|
||||
*
|
||||
* Copyright (C) 2014 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/input.h>
|
||||
#include <linux/keycombo.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
struct keycombo_state {
|
||||
struct input_handler input_handler;
|
||||
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
|
||||
unsigned long upbit[BITS_TO_LONGS(KEY_CNT)];
|
||||
unsigned long key[BITS_TO_LONGS(KEY_CNT)];
|
||||
spinlock_t lock;
|
||||
struct workqueue_struct *wq;
|
||||
int key_down_target;
|
||||
int key_down;
|
||||
int key_up;
|
||||
struct delayed_work key_down_work;
|
||||
int delay;
|
||||
struct work_struct key_up_work;
|
||||
void (*key_up_fn)(void *);
|
||||
void (*key_down_fn)(void *);
|
||||
void *priv;
|
||||
int key_is_down;
|
||||
struct wakeup_source combo_held_wake_source;
|
||||
struct wakeup_source combo_up_wake_source;
|
||||
};
|
||||
|
||||
static void do_key_down(struct work_struct *work)
|
||||
{
|
||||
struct delayed_work *dwork = container_of(work, struct delayed_work,
|
||||
work);
|
||||
struct keycombo_state *state = container_of(dwork,
|
||||
struct keycombo_state, key_down_work);
|
||||
if (state->key_down_fn)
|
||||
state->key_down_fn(state->priv);
|
||||
}
|
||||
|
||||
static void do_key_up(struct work_struct *work)
|
||||
{
|
||||
struct keycombo_state *state = container_of(work, struct keycombo_state,
|
||||
key_up_work);
|
||||
if (state->key_up_fn)
|
||||
state->key_up_fn(state->priv);
|
||||
__pm_relax(&state->combo_up_wake_source);
|
||||
}
|
||||
|
||||
static void keycombo_event(struct input_handle *handle, unsigned int type,
|
||||
unsigned int code, int value)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct keycombo_state *state = handle->private;
|
||||
|
||||
if (type != EV_KEY)
|
||||
return;
|
||||
|
||||
if (code >= KEY_MAX)
|
||||
return;
|
||||
|
||||
if (!test_bit(code, state->keybit))
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&state->lock, flags);
|
||||
if (!test_bit(code, state->key) == !value)
|
||||
goto done;
|
||||
__change_bit(code, state->key);
|
||||
if (test_bit(code, state->upbit)) {
|
||||
if (value)
|
||||
state->key_up++;
|
||||
else
|
||||
state->key_up--;
|
||||
} else {
|
||||
if (value)
|
||||
state->key_down++;
|
||||
else
|
||||
state->key_down--;
|
||||
}
|
||||
if (state->key_down == state->key_down_target && state->key_up == 0) {
|
||||
__pm_stay_awake(&state->combo_held_wake_source);
|
||||
state->key_is_down = 1;
|
||||
if (queue_delayed_work(state->wq, &state->key_down_work,
|
||||
state->delay))
|
||||
pr_debug("Key down work already queued!");
|
||||
} else if (state->key_is_down) {
|
||||
if (!cancel_delayed_work(&state->key_down_work)) {
|
||||
__pm_stay_awake(&state->combo_up_wake_source);
|
||||
queue_work(state->wq, &state->key_up_work);
|
||||
}
|
||||
__pm_relax(&state->combo_held_wake_source);
|
||||
state->key_is_down = 0;
|
||||
}
|
||||
done:
|
||||
spin_unlock_irqrestore(&state->lock, flags);
|
||||
}
|
||||
|
||||
static int keycombo_connect(struct input_handler *handler,
|
||||
struct input_dev *dev,
|
||||
const struct input_device_id *id)
|
||||
{
|
||||
int i;
|
||||
int ret;
|
||||
struct input_handle *handle;
|
||||
struct keycombo_state *state =
|
||||
container_of(handler, struct keycombo_state, input_handler);
|
||||
for (i = 0; i < KEY_MAX; i++) {
|
||||
if (test_bit(i, state->keybit) && test_bit(i, dev->keybit))
|
||||
break;
|
||||
}
|
||||
if (i == KEY_MAX)
|
||||
return -ENODEV;
|
||||
|
||||
handle = kzalloc(sizeof(*handle), GFP_KERNEL);
|
||||
if (!handle)
|
||||
return -ENOMEM;
|
||||
|
||||
handle->dev = dev;
|
||||
handle->handler = handler;
|
||||
handle->name = KEYCOMBO_NAME;
|
||||
handle->private = state;
|
||||
|
||||
ret = input_register_handle(handle);
|
||||
if (ret)
|
||||
goto err_input_register_handle;
|
||||
|
||||
ret = input_open_device(handle);
|
||||
if (ret)
|
||||
goto err_input_open_device;
|
||||
|
||||
return 0;
|
||||
|
||||
err_input_open_device:
|
||||
input_unregister_handle(handle);
|
||||
err_input_register_handle:
|
||||
kfree(handle);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void keycombo_disconnect(struct input_handle *handle)
|
||||
{
|
||||
input_close_device(handle);
|
||||
input_unregister_handle(handle);
|
||||
kfree(handle);
|
||||
}
|
||||
|
||||
static const struct input_device_id keycombo_ids[] = {
|
||||
{
|
||||
.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
|
||||
.evbit = { BIT_MASK(EV_KEY) },
|
||||
},
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(input, keycombo_ids);
|
||||
|
||||
static int keycombo_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
int key, *keyp;
|
||||
struct keycombo_state *state;
|
||||
struct keycombo_platform_data *pdata = pdev->dev.platform_data;
|
||||
|
||||
if (!pdata)
|
||||
return -EINVAL;
|
||||
|
||||
state = kzalloc(sizeof(*state), GFP_KERNEL);
|
||||
if (!state)
|
||||
return -ENOMEM;
|
||||
|
||||
spin_lock_init(&state->lock);
|
||||
keyp = pdata->keys_down;
|
||||
while ((key = *keyp++)) {
|
||||
if (key >= KEY_MAX)
|
||||
continue;
|
||||
state->key_down_target++;
|
||||
__set_bit(key, state->keybit);
|
||||
}
|
||||
if (pdata->keys_up) {
|
||||
keyp = pdata->keys_up;
|
||||
while ((key = *keyp++)) {
|
||||
if (key >= KEY_MAX)
|
||||
continue;
|
||||
__set_bit(key, state->keybit);
|
||||
__set_bit(key, state->upbit);
|
||||
}
|
||||
}
|
||||
|
||||
state->wq = alloc_ordered_workqueue("keycombo", 0);
|
||||
if (!state->wq)
|
||||
return -ENOMEM;
|
||||
|
||||
state->priv = pdata->priv;
|
||||
|
||||
if (pdata->key_down_fn)
|
||||
state->key_down_fn = pdata->key_down_fn;
|
||||
INIT_DELAYED_WORK(&state->key_down_work, do_key_down);
|
||||
|
||||
if (pdata->key_up_fn)
|
||||
state->key_up_fn = pdata->key_up_fn;
|
||||
INIT_WORK(&state->key_up_work, do_key_up);
|
||||
|
||||
wakeup_source_init(&state->combo_held_wake_source, "key combo");
|
||||
wakeup_source_init(&state->combo_up_wake_source, "key combo up");
|
||||
state->delay = msecs_to_jiffies(pdata->key_down_delay);
|
||||
|
||||
state->input_handler.event = keycombo_event;
|
||||
state->input_handler.connect = keycombo_connect;
|
||||
state->input_handler.disconnect = keycombo_disconnect;
|
||||
state->input_handler.name = KEYCOMBO_NAME;
|
||||
state->input_handler.id_table = keycombo_ids;
|
||||
ret = input_register_handler(&state->input_handler);
|
||||
if (ret) {
|
||||
kfree(state);
|
||||
return ret;
|
||||
}
|
||||
platform_set_drvdata(pdev, state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int keycombo_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct keycombo_state *state = platform_get_drvdata(pdev);
|
||||
input_unregister_handler(&state->input_handler);
|
||||
destroy_workqueue(state->wq);
|
||||
kfree(state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
struct platform_driver keycombo_driver = {
|
||||
.driver.name = KEYCOMBO_NAME,
|
||||
.probe = keycombo_probe,
|
||||
.remove = keycombo_remove,
|
||||
};
|
||||
|
||||
static int __init keycombo_init(void)
|
||||
{
|
||||
return platform_driver_register(&keycombo_driver);
|
||||
}
|
||||
|
||||
static void __exit keycombo_exit(void)
|
||||
{
|
||||
return platform_driver_unregister(&keycombo_driver);
|
||||
}
|
||||
|
||||
module_init(keycombo_init);
|
||||
module_exit(keycombo_exit);
|
||||
145
drivers/input/keyreset.c
Normal file
145
drivers/input/keyreset.c
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/* drivers/input/keyreset.c
|
||||
*
|
||||
* Copyright (C) 2014 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/input.h>
|
||||
#include <linux/keyreset.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/syscalls.h>
|
||||
#include <linux/keycombo.h>
|
||||
|
||||
struct keyreset_state {
|
||||
int restart_requested;
|
||||
int (*reset_fn)(void);
|
||||
struct platform_device *pdev_child;
|
||||
struct work_struct restart_work;
|
||||
};
|
||||
|
||||
static void do_restart(struct work_struct *unused)
|
||||
{
|
||||
sys_sync();
|
||||
kernel_restart(NULL);
|
||||
}
|
||||
|
||||
static void do_reset_fn(void *priv)
|
||||
{
|
||||
struct keyreset_state *state = priv;
|
||||
if (state->restart_requested)
|
||||
panic("keyboard reset failed, %d", state->restart_requested);
|
||||
if (state->reset_fn) {
|
||||
state->restart_requested = state->reset_fn();
|
||||
} else {
|
||||
pr_info("keyboard reset\n");
|
||||
schedule_work(&state->restart_work);
|
||||
state->restart_requested = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int keyreset_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = -ENOMEM;
|
||||
struct keycombo_platform_data *pdata_child;
|
||||
struct keyreset_platform_data *pdata = pdev->dev.platform_data;
|
||||
int up_size = 0, down_size = 0, size;
|
||||
int key, *keyp;
|
||||
struct keyreset_state *state;
|
||||
|
||||
if (!pdata)
|
||||
return -EINVAL;
|
||||
state = devm_kzalloc(&pdev->dev, sizeof(*state), GFP_KERNEL);
|
||||
if (!state)
|
||||
return -ENOMEM;
|
||||
|
||||
state->pdev_child = platform_device_alloc(KEYCOMBO_NAME,
|
||||
PLATFORM_DEVID_AUTO);
|
||||
if (!state->pdev_child)
|
||||
return -ENOMEM;
|
||||
state->pdev_child->dev.parent = &pdev->dev;
|
||||
INIT_WORK(&state->restart_work, do_restart);
|
||||
|
||||
keyp = pdata->keys_down;
|
||||
while ((key = *keyp++)) {
|
||||
if (key >= KEY_MAX)
|
||||
continue;
|
||||
down_size++;
|
||||
}
|
||||
if (pdata->keys_up) {
|
||||
keyp = pdata->keys_up;
|
||||
while ((key = *keyp++)) {
|
||||
if (key >= KEY_MAX)
|
||||
continue;
|
||||
up_size++;
|
||||
}
|
||||
}
|
||||
size = sizeof(struct keycombo_platform_data)
|
||||
+ sizeof(int) * (down_size + 1);
|
||||
pdata_child = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
|
||||
if (!pdata_child)
|
||||
goto error;
|
||||
memcpy(pdata_child->keys_down, pdata->keys_down,
|
||||
sizeof(int) * down_size);
|
||||
if (up_size > 0) {
|
||||
pdata_child->keys_up = devm_kzalloc(&pdev->dev, up_size + 1,
|
||||
GFP_KERNEL);
|
||||
if (!pdata_child->keys_up)
|
||||
goto error;
|
||||
memcpy(pdata_child->keys_up, pdata->keys_up,
|
||||
sizeof(int) * up_size);
|
||||
if (!pdata_child->keys_up)
|
||||
goto error;
|
||||
}
|
||||
state->reset_fn = pdata->reset_fn;
|
||||
pdata_child->key_down_fn = do_reset_fn;
|
||||
pdata_child->priv = state;
|
||||
pdata_child->key_down_delay = pdata->key_down_delay;
|
||||
ret = platform_device_add_data(state->pdev_child, pdata_child, size);
|
||||
if (ret)
|
||||
goto error;
|
||||
platform_set_drvdata(pdev, state);
|
||||
return platform_device_add(state->pdev_child);
|
||||
error:
|
||||
platform_device_put(state->pdev_child);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int keyreset_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct keyreset_state *state = platform_get_drvdata(pdev);
|
||||
platform_device_put(state->pdev_child);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
struct platform_driver keyreset_driver = {
|
||||
.driver.name = KEYRESET_NAME,
|
||||
.probe = keyreset_probe,
|
||||
.remove = keyreset_remove,
|
||||
};
|
||||
|
||||
static int __init keyreset_init(void)
|
||||
{
|
||||
return platform_driver_register(&keyreset_driver);
|
||||
}
|
||||
|
||||
static void __exit keyreset_exit(void)
|
||||
{
|
||||
return platform_driver_unregister(&keyreset_driver);
|
||||
}
|
||||
|
||||
module_init(keyreset_init);
|
||||
module_exit(keyreset_exit);
|
||||
|
|
@ -341,6 +341,17 @@ config INPUT_ATI_REMOTE2
|
|||
To compile this driver as a module, choose M here: the module will be
|
||||
called ati_remote2.
|
||||
|
||||
config INPUT_KEYCHORD
|
||||
tristate "Key chord input driver support"
|
||||
help
|
||||
Say Y here if you want to enable the key chord driver
|
||||
accessible at /dev/keychord. This driver can be used
|
||||
for receiving notifications when client specified key
|
||||
combinations are pressed.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called keychord.
|
||||
|
||||
config INPUT_KEYSPAN_REMOTE
|
||||
tristate "Keyspan DMR USB remote control"
|
||||
depends on USB_ARCH_HAS_HCD
|
||||
|
|
@ -509,6 +520,11 @@ config INPUT_SGI_BTNS
|
|||
To compile this driver as a module, choose M here: the
|
||||
module will be called sgi_btns.
|
||||
|
||||
config INPUT_GPIO
|
||||
tristate "GPIO driver support"
|
||||
help
|
||||
Say Y here if you want to support gpio based keys, wheels etc...
|
||||
|
||||
config HP_SDC_RTC
|
||||
tristate "HP SDC Real Time Clock"
|
||||
depends on (GSC || HP300) && SERIO
|
||||
|
|
|
|||
|
|
@ -34,9 +34,11 @@ obj-$(CONFIG_INPUT_DRV2667_HAPTICS) += drv2667.o
|
|||
obj-$(CONFIG_INPUT_GP2A) += gp2ap002a00f.o
|
||||
obj-$(CONFIG_INPUT_GPIO_BEEPER) += gpio-beeper.o
|
||||
obj-$(CONFIG_INPUT_GPIO_TILT_POLLED) += gpio_tilt_polled.o
|
||||
obj-$(CONFIG_INPUT_GPIO) += gpio_event.o gpio_matrix.o gpio_input.o gpio_output.o gpio_axis.o
|
||||
obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o
|
||||
obj-$(CONFIG_INPUT_IMS_PCU) += ims-pcu.o
|
||||
obj-$(CONFIG_INPUT_IXP4XX_BEEPER) += ixp4xx-beeper.o
|
||||
obj-$(CONFIG_INPUT_KEYCHORD) += keychord.o
|
||||
obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o
|
||||
obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o
|
||||
obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o
|
||||
|
|
|
|||
192
drivers/input/misc/gpio_axis.c
Normal file
192
drivers/input/misc/gpio_axis.c
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
/* drivers/input/misc/gpio_axis.c
|
||||
*
|
||||
* Copyright (C) 2007 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio_event.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
struct gpio_axis_state {
|
||||
struct gpio_event_input_devs *input_devs;
|
||||
struct gpio_event_axis_info *info;
|
||||
uint32_t pos;
|
||||
};
|
||||
|
||||
uint16_t gpio_axis_4bit_gray_map_table[] = {
|
||||
[0x0] = 0x0, [0x1] = 0x1, /* 0000 0001 */
|
||||
[0x3] = 0x2, [0x2] = 0x3, /* 0011 0010 */
|
||||
[0x6] = 0x4, [0x7] = 0x5, /* 0110 0111 */
|
||||
[0x5] = 0x6, [0x4] = 0x7, /* 0101 0100 */
|
||||
[0xc] = 0x8, [0xd] = 0x9, /* 1100 1101 */
|
||||
[0xf] = 0xa, [0xe] = 0xb, /* 1111 1110 */
|
||||
[0xa] = 0xc, [0xb] = 0xd, /* 1010 1011 */
|
||||
[0x9] = 0xe, [0x8] = 0xf, /* 1001 1000 */
|
||||
};
|
||||
uint16_t gpio_axis_4bit_gray_map(struct gpio_event_axis_info *info, uint16_t in)
|
||||
{
|
||||
return gpio_axis_4bit_gray_map_table[in];
|
||||
}
|
||||
|
||||
uint16_t gpio_axis_5bit_singletrack_map_table[] = {
|
||||
[0x10] = 0x00, [0x14] = 0x01, [0x1c] = 0x02, /* 10000 10100 11100 */
|
||||
[0x1e] = 0x03, [0x1a] = 0x04, [0x18] = 0x05, /* 11110 11010 11000 */
|
||||
[0x08] = 0x06, [0x0a] = 0x07, [0x0e] = 0x08, /* 01000 01010 01110 */
|
||||
[0x0f] = 0x09, [0x0d] = 0x0a, [0x0c] = 0x0b, /* 01111 01101 01100 */
|
||||
[0x04] = 0x0c, [0x05] = 0x0d, [0x07] = 0x0e, /* 00100 00101 00111 */
|
||||
[0x17] = 0x0f, [0x16] = 0x10, [0x06] = 0x11, /* 10111 10110 00110 */
|
||||
[0x02] = 0x12, [0x12] = 0x13, [0x13] = 0x14, /* 00010 10010 10011 */
|
||||
[0x1b] = 0x15, [0x0b] = 0x16, [0x03] = 0x17, /* 11011 01011 00011 */
|
||||
[0x01] = 0x18, [0x09] = 0x19, [0x19] = 0x1a, /* 00001 01001 11001 */
|
||||
[0x1d] = 0x1b, [0x15] = 0x1c, [0x11] = 0x1d, /* 11101 10101 10001 */
|
||||
};
|
||||
uint16_t gpio_axis_5bit_singletrack_map(
|
||||
struct gpio_event_axis_info *info, uint16_t in)
|
||||
{
|
||||
return gpio_axis_5bit_singletrack_map_table[in];
|
||||
}
|
||||
|
||||
static void gpio_event_update_axis(struct gpio_axis_state *as, int report)
|
||||
{
|
||||
struct gpio_event_axis_info *ai = as->info;
|
||||
int i;
|
||||
int change;
|
||||
uint16_t state = 0;
|
||||
uint16_t pos;
|
||||
uint16_t old_pos = as->pos;
|
||||
for (i = ai->count - 1; i >= 0; i--)
|
||||
state = (state << 1) | gpio_get_value(ai->gpio[i]);
|
||||
pos = ai->map(ai, state);
|
||||
if (ai->flags & GPIOEAF_PRINT_RAW)
|
||||
pr_info("axis %d-%d raw %x, pos %d -> %d\n",
|
||||
ai->type, ai->code, state, old_pos, pos);
|
||||
if (report && pos != old_pos) {
|
||||
if (ai->type == EV_REL) {
|
||||
change = (ai->decoded_size + pos - old_pos) %
|
||||
ai->decoded_size;
|
||||
if (change > ai->decoded_size / 2)
|
||||
change -= ai->decoded_size;
|
||||
if (change == ai->decoded_size / 2) {
|
||||
if (ai->flags & GPIOEAF_PRINT_EVENT)
|
||||
pr_info("axis %d-%d unknown direction, "
|
||||
"pos %d -> %d\n", ai->type,
|
||||
ai->code, old_pos, pos);
|
||||
change = 0; /* no closest direction */
|
||||
}
|
||||
if (ai->flags & GPIOEAF_PRINT_EVENT)
|
||||
pr_info("axis %d-%d change %d\n",
|
||||
ai->type, ai->code, change);
|
||||
input_report_rel(as->input_devs->dev[ai->dev],
|
||||
ai->code, change);
|
||||
} else {
|
||||
if (ai->flags & GPIOEAF_PRINT_EVENT)
|
||||
pr_info("axis %d-%d now %d\n",
|
||||
ai->type, ai->code, pos);
|
||||
input_event(as->input_devs->dev[ai->dev],
|
||||
ai->type, ai->code, pos);
|
||||
}
|
||||
input_sync(as->input_devs->dev[ai->dev]);
|
||||
}
|
||||
as->pos = pos;
|
||||
}
|
||||
|
||||
static irqreturn_t gpio_axis_irq_handler(int irq, void *dev_id)
|
||||
{
|
||||
struct gpio_axis_state *as = dev_id;
|
||||
gpio_event_update_axis(as, 1);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
int gpio_event_axis_func(struct gpio_event_input_devs *input_devs,
|
||||
struct gpio_event_info *info, void **data, int func)
|
||||
{
|
||||
int ret;
|
||||
int i;
|
||||
int irq;
|
||||
struct gpio_event_axis_info *ai;
|
||||
struct gpio_axis_state *as;
|
||||
|
||||
ai = container_of(info, struct gpio_event_axis_info, info);
|
||||
if (func == GPIO_EVENT_FUNC_SUSPEND) {
|
||||
for (i = 0; i < ai->count; i++)
|
||||
disable_irq(gpio_to_irq(ai->gpio[i]));
|
||||
return 0;
|
||||
}
|
||||
if (func == GPIO_EVENT_FUNC_RESUME) {
|
||||
for (i = 0; i < ai->count; i++)
|
||||
enable_irq(gpio_to_irq(ai->gpio[i]));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (func == GPIO_EVENT_FUNC_INIT) {
|
||||
*data = as = kmalloc(sizeof(*as), GFP_KERNEL);
|
||||
if (as == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto err_alloc_axis_state_failed;
|
||||
}
|
||||
as->input_devs = input_devs;
|
||||
as->info = ai;
|
||||
if (ai->dev >= input_devs->count) {
|
||||
pr_err("gpio_event_axis: bad device index %d >= %d "
|
||||
"for %d:%d\n", ai->dev, input_devs->count,
|
||||
ai->type, ai->code);
|
||||
ret = -EINVAL;
|
||||
goto err_bad_device_index;
|
||||
}
|
||||
|
||||
input_set_capability(input_devs->dev[ai->dev],
|
||||
ai->type, ai->code);
|
||||
if (ai->type == EV_ABS) {
|
||||
input_set_abs_params(input_devs->dev[ai->dev], ai->code,
|
||||
0, ai->decoded_size - 1, 0, 0);
|
||||
}
|
||||
for (i = 0; i < ai->count; i++) {
|
||||
ret = gpio_request(ai->gpio[i], "gpio_event_axis");
|
||||
if (ret < 0)
|
||||
goto err_request_gpio_failed;
|
||||
ret = gpio_direction_input(ai->gpio[i]);
|
||||
if (ret < 0)
|
||||
goto err_gpio_direction_input_failed;
|
||||
ret = irq = gpio_to_irq(ai->gpio[i]);
|
||||
if (ret < 0)
|
||||
goto err_get_irq_num_failed;
|
||||
ret = request_irq(irq, gpio_axis_irq_handler,
|
||||
IRQF_TRIGGER_RISING |
|
||||
IRQF_TRIGGER_FALLING,
|
||||
"gpio_event_axis", as);
|
||||
if (ret < 0)
|
||||
goto err_request_irq_failed;
|
||||
}
|
||||
gpio_event_update_axis(as, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
as = *data;
|
||||
for (i = ai->count - 1; i >= 0; i--) {
|
||||
free_irq(gpio_to_irq(ai->gpio[i]), as);
|
||||
err_request_irq_failed:
|
||||
err_get_irq_num_failed:
|
||||
err_gpio_direction_input_failed:
|
||||
gpio_free(ai->gpio[i]);
|
||||
err_request_gpio_failed:
|
||||
;
|
||||
}
|
||||
err_bad_device_index:
|
||||
kfree(as);
|
||||
*data = NULL;
|
||||
err_alloc_axis_state_failed:
|
||||
return ret;
|
||||
}
|
||||
228
drivers/input/misc/gpio_event.c
Normal file
228
drivers/input/misc/gpio_event.c
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
/* drivers/input/misc/gpio_event.c
|
||||
*
|
||||
* Copyright (C) 2007 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/gpio_event.h>
|
||||
#include <linux/hrtimer.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
struct gpio_event {
|
||||
struct gpio_event_input_devs *input_devs;
|
||||
const struct gpio_event_platform_data *info;
|
||||
void *state[0];
|
||||
};
|
||||
|
||||
static int gpio_input_event(
|
||||
struct input_dev *dev, unsigned int type, unsigned int code, int value)
|
||||
{
|
||||
int i;
|
||||
int devnr;
|
||||
int ret = 0;
|
||||
int tmp_ret;
|
||||
struct gpio_event_info **ii;
|
||||
struct gpio_event *ip = input_get_drvdata(dev);
|
||||
|
||||
for (devnr = 0; devnr < ip->input_devs->count; devnr++)
|
||||
if (ip->input_devs->dev[devnr] == dev)
|
||||
break;
|
||||
if (devnr == ip->input_devs->count) {
|
||||
pr_err("gpio_input_event: unknown device %p\n", dev);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
for (i = 0, ii = ip->info->info; i < ip->info->info_count; i++, ii++) {
|
||||
if ((*ii)->event) {
|
||||
tmp_ret = (*ii)->event(ip->input_devs, *ii,
|
||||
&ip->state[i],
|
||||
devnr, type, code, value);
|
||||
if (tmp_ret)
|
||||
ret = tmp_ret;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int gpio_event_call_all_func(struct gpio_event *ip, int func)
|
||||
{
|
||||
int i;
|
||||
int ret;
|
||||
struct gpio_event_info **ii;
|
||||
|
||||
if (func == GPIO_EVENT_FUNC_INIT || func == GPIO_EVENT_FUNC_RESUME) {
|
||||
ii = ip->info->info;
|
||||
for (i = 0; i < ip->info->info_count; i++, ii++) {
|
||||
if ((*ii)->func == NULL) {
|
||||
ret = -ENODEV;
|
||||
pr_err("gpio_event_probe: Incomplete pdata, "
|
||||
"no function\n");
|
||||
goto err_no_func;
|
||||
}
|
||||
if (func == GPIO_EVENT_FUNC_RESUME && (*ii)->no_suspend)
|
||||
continue;
|
||||
ret = (*ii)->func(ip->input_devs, *ii, &ip->state[i],
|
||||
func);
|
||||
if (ret) {
|
||||
pr_err("gpio_event_probe: function failed\n");
|
||||
goto err_func_failed;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
i = ip->info->info_count;
|
||||
ii = ip->info->info + i;
|
||||
while (i > 0) {
|
||||
i--;
|
||||
ii--;
|
||||
if ((func & ~1) == GPIO_EVENT_FUNC_SUSPEND && (*ii)->no_suspend)
|
||||
continue;
|
||||
(*ii)->func(ip->input_devs, *ii, &ip->state[i], func & ~1);
|
||||
err_func_failed:
|
||||
err_no_func:
|
||||
;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __maybe_unused gpio_event_suspend(struct gpio_event *ip)
|
||||
{
|
||||
gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_SUSPEND);
|
||||
if (ip->info->power)
|
||||
ip->info->power(ip->info, 0);
|
||||
}
|
||||
|
||||
static void __maybe_unused gpio_event_resume(struct gpio_event *ip)
|
||||
{
|
||||
if (ip->info->power)
|
||||
ip->info->power(ip->info, 1);
|
||||
gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_RESUME);
|
||||
}
|
||||
|
||||
static int gpio_event_probe(struct platform_device *pdev)
|
||||
{
|
||||
int err;
|
||||
struct gpio_event *ip;
|
||||
struct gpio_event_platform_data *event_info;
|
||||
int dev_count = 1;
|
||||
int i;
|
||||
int registered = 0;
|
||||
|
||||
event_info = pdev->dev.platform_data;
|
||||
if (event_info == NULL) {
|
||||
pr_err("gpio_event_probe: No pdata\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
if ((!event_info->name && !event_info->names[0]) ||
|
||||
!event_info->info || !event_info->info_count) {
|
||||
pr_err("gpio_event_probe: Incomplete pdata\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
if (!event_info->name)
|
||||
while (event_info->names[dev_count])
|
||||
dev_count++;
|
||||
ip = kzalloc(sizeof(*ip) +
|
||||
sizeof(ip->state[0]) * event_info->info_count +
|
||||
sizeof(*ip->input_devs) +
|
||||
sizeof(ip->input_devs->dev[0]) * dev_count, GFP_KERNEL);
|
||||
if (ip == NULL) {
|
||||
err = -ENOMEM;
|
||||
pr_err("gpio_event_probe: Failed to allocate private data\n");
|
||||
goto err_kp_alloc_failed;
|
||||
}
|
||||
ip->input_devs = (void*)&ip->state[event_info->info_count];
|
||||
platform_set_drvdata(pdev, ip);
|
||||
|
||||
for (i = 0; i < dev_count; i++) {
|
||||
struct input_dev *input_dev = input_allocate_device();
|
||||
if (input_dev == NULL) {
|
||||
err = -ENOMEM;
|
||||
pr_err("gpio_event_probe: "
|
||||
"Failed to allocate input device\n");
|
||||
goto err_input_dev_alloc_failed;
|
||||
}
|
||||
input_set_drvdata(input_dev, ip);
|
||||
input_dev->name = event_info->name ?
|
||||
event_info->name : event_info->names[i];
|
||||
input_dev->event = gpio_input_event;
|
||||
ip->input_devs->dev[i] = input_dev;
|
||||
}
|
||||
ip->input_devs->count = dev_count;
|
||||
ip->info = event_info;
|
||||
if (event_info->power)
|
||||
ip->info->power(ip->info, 1);
|
||||
|
||||
err = gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_INIT);
|
||||
if (err)
|
||||
goto err_call_all_func_failed;
|
||||
|
||||
for (i = 0; i < dev_count; i++) {
|
||||
err = input_register_device(ip->input_devs->dev[i]);
|
||||
if (err) {
|
||||
pr_err("gpio_event_probe: Unable to register %s "
|
||||
"input device\n", ip->input_devs->dev[i]->name);
|
||||
goto err_input_register_device_failed;
|
||||
}
|
||||
registered++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_input_register_device_failed:
|
||||
gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_UNINIT);
|
||||
err_call_all_func_failed:
|
||||
if (event_info->power)
|
||||
ip->info->power(ip->info, 0);
|
||||
for (i = 0; i < registered; i++)
|
||||
input_unregister_device(ip->input_devs->dev[i]);
|
||||
for (i = dev_count - 1; i >= registered; i--) {
|
||||
input_free_device(ip->input_devs->dev[i]);
|
||||
err_input_dev_alloc_failed:
|
||||
;
|
||||
}
|
||||
kfree(ip);
|
||||
err_kp_alloc_failed:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int gpio_event_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct gpio_event *ip = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_UNINIT);
|
||||
if (ip->info->power)
|
||||
ip->info->power(ip->info, 0);
|
||||
for (i = 0; i < ip->input_devs->count; i++)
|
||||
input_unregister_device(ip->input_devs->dev[i]);
|
||||
kfree(ip);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver gpio_event_driver = {
|
||||
.probe = gpio_event_probe,
|
||||
.remove = gpio_event_remove,
|
||||
.driver = {
|
||||
.name = GPIO_EVENT_DEV_NAME,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(gpio_event_driver);
|
||||
|
||||
MODULE_DESCRIPTION("GPIO Event Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
390
drivers/input/misc/gpio_input.c
Normal file
390
drivers/input/misc/gpio_input.c
Normal file
|
|
@ -0,0 +1,390 @@
|
|||
/* drivers/input/misc/gpio_input.c
|
||||
*
|
||||
* Copyright (C) 2007 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio_event.h>
|
||||
#include <linux/hrtimer.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/pm_wakeup.h>
|
||||
|
||||
enum {
|
||||
DEBOUNCE_UNSTABLE = BIT(0), /* Got irq, while debouncing */
|
||||
DEBOUNCE_PRESSED = BIT(1),
|
||||
DEBOUNCE_NOTPRESSED = BIT(2),
|
||||
DEBOUNCE_WAIT_IRQ = BIT(3), /* Stable irq state */
|
||||
DEBOUNCE_POLL = BIT(4), /* Stable polling state */
|
||||
|
||||
DEBOUNCE_UNKNOWN =
|
||||
DEBOUNCE_PRESSED | DEBOUNCE_NOTPRESSED,
|
||||
};
|
||||
|
||||
struct gpio_key_state {
|
||||
struct gpio_input_state *ds;
|
||||
uint8_t debounce;
|
||||
};
|
||||
|
||||
struct gpio_input_state {
|
||||
struct gpio_event_input_devs *input_devs;
|
||||
const struct gpio_event_input_info *info;
|
||||
struct hrtimer timer;
|
||||
int use_irq;
|
||||
int debounce_count;
|
||||
spinlock_t irq_lock;
|
||||
struct wakeup_source *ws;
|
||||
struct gpio_key_state key_state[0];
|
||||
};
|
||||
|
||||
static enum hrtimer_restart gpio_event_input_timer_func(struct hrtimer *timer)
|
||||
{
|
||||
int i;
|
||||
int pressed;
|
||||
struct gpio_input_state *ds =
|
||||
container_of(timer, struct gpio_input_state, timer);
|
||||
unsigned gpio_flags = ds->info->flags;
|
||||
unsigned npolarity;
|
||||
int nkeys = ds->info->keymap_size;
|
||||
const struct gpio_event_direct_entry *key_entry;
|
||||
struct gpio_key_state *key_state;
|
||||
unsigned long irqflags;
|
||||
uint8_t debounce;
|
||||
bool sync_needed;
|
||||
|
||||
#if 0
|
||||
key_entry = kp->keys_info->keymap;
|
||||
key_state = kp->key_state;
|
||||
for (i = 0; i < nkeys; i++, key_entry++, key_state++)
|
||||
pr_info("gpio_read_detect_status %d %d\n", key_entry->gpio,
|
||||
gpio_read_detect_status(key_entry->gpio));
|
||||
#endif
|
||||
key_entry = ds->info->keymap;
|
||||
key_state = ds->key_state;
|
||||
sync_needed = false;
|
||||
spin_lock_irqsave(&ds->irq_lock, irqflags);
|
||||
for (i = 0; i < nkeys; i++, key_entry++, key_state++) {
|
||||
debounce = key_state->debounce;
|
||||
if (debounce & DEBOUNCE_WAIT_IRQ)
|
||||
continue;
|
||||
if (key_state->debounce & DEBOUNCE_UNSTABLE) {
|
||||
debounce = key_state->debounce = DEBOUNCE_UNKNOWN;
|
||||
enable_irq(gpio_to_irq(key_entry->gpio));
|
||||
if (gpio_flags & GPIOEDF_PRINT_KEY_UNSTABLE)
|
||||
pr_info("gpio_keys_scan_keys: key %x-%x, %d "
|
||||
"(%d) continue debounce\n",
|
||||
ds->info->type, key_entry->code,
|
||||
i, key_entry->gpio);
|
||||
}
|
||||
npolarity = !(gpio_flags & GPIOEDF_ACTIVE_HIGH);
|
||||
pressed = gpio_get_value(key_entry->gpio) ^ npolarity;
|
||||
if (debounce & DEBOUNCE_POLL) {
|
||||
if (pressed == !(debounce & DEBOUNCE_PRESSED)) {
|
||||
ds->debounce_count++;
|
||||
key_state->debounce = DEBOUNCE_UNKNOWN;
|
||||
if (gpio_flags & GPIOEDF_PRINT_KEY_DEBOUNCE)
|
||||
pr_info("gpio_keys_scan_keys: key %x-"
|
||||
"%x, %d (%d) start debounce\n",
|
||||
ds->info->type, key_entry->code,
|
||||
i, key_entry->gpio);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (pressed && (debounce & DEBOUNCE_NOTPRESSED)) {
|
||||
if (gpio_flags & GPIOEDF_PRINT_KEY_DEBOUNCE)
|
||||
pr_info("gpio_keys_scan_keys: key %x-%x, %d "
|
||||
"(%d) debounce pressed 1\n",
|
||||
ds->info->type, key_entry->code,
|
||||
i, key_entry->gpio);
|
||||
key_state->debounce = DEBOUNCE_PRESSED;
|
||||
continue;
|
||||
}
|
||||
if (!pressed && (debounce & DEBOUNCE_PRESSED)) {
|
||||
if (gpio_flags & GPIOEDF_PRINT_KEY_DEBOUNCE)
|
||||
pr_info("gpio_keys_scan_keys: key %x-%x, %d "
|
||||
"(%d) debounce pressed 0\n",
|
||||
ds->info->type, key_entry->code,
|
||||
i, key_entry->gpio);
|
||||
key_state->debounce = DEBOUNCE_NOTPRESSED;
|
||||
continue;
|
||||
}
|
||||
/* key is stable */
|
||||
ds->debounce_count--;
|
||||
if (ds->use_irq)
|
||||
key_state->debounce |= DEBOUNCE_WAIT_IRQ;
|
||||
else
|
||||
key_state->debounce |= DEBOUNCE_POLL;
|
||||
if (gpio_flags & GPIOEDF_PRINT_KEYS)
|
||||
pr_info("gpio_keys_scan_keys: key %x-%x, %d (%d) "
|
||||
"changed to %d\n", ds->info->type,
|
||||
key_entry->code, i, key_entry->gpio, pressed);
|
||||
input_event(ds->input_devs->dev[key_entry->dev], ds->info->type,
|
||||
key_entry->code, pressed);
|
||||
sync_needed = true;
|
||||
}
|
||||
if (sync_needed) {
|
||||
for (i = 0; i < ds->input_devs->count; i++)
|
||||
input_sync(ds->input_devs->dev[i]);
|
||||
}
|
||||
|
||||
#if 0
|
||||
key_entry = kp->keys_info->keymap;
|
||||
key_state = kp->key_state;
|
||||
for (i = 0; i < nkeys; i++, key_entry++, key_state++) {
|
||||
pr_info("gpio_read_detect_status %d %d\n", key_entry->gpio,
|
||||
gpio_read_detect_status(key_entry->gpio));
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ds->debounce_count)
|
||||
hrtimer_start(timer, ds->info->debounce_time, HRTIMER_MODE_REL);
|
||||
else if (!ds->use_irq)
|
||||
hrtimer_start(timer, ds->info->poll_time, HRTIMER_MODE_REL);
|
||||
else
|
||||
__pm_relax(ds->ws);
|
||||
|
||||
spin_unlock_irqrestore(&ds->irq_lock, irqflags);
|
||||
|
||||
return HRTIMER_NORESTART;
|
||||
}
|
||||
|
||||
static irqreturn_t gpio_event_input_irq_handler(int irq, void *dev_id)
|
||||
{
|
||||
struct gpio_key_state *ks = dev_id;
|
||||
struct gpio_input_state *ds = ks->ds;
|
||||
int keymap_index = ks - ds->key_state;
|
||||
const struct gpio_event_direct_entry *key_entry;
|
||||
unsigned long irqflags;
|
||||
int pressed;
|
||||
|
||||
if (!ds->use_irq)
|
||||
return IRQ_HANDLED;
|
||||
|
||||
key_entry = &ds->info->keymap[keymap_index];
|
||||
|
||||
if (ds->info->debounce_time.tv64) {
|
||||
spin_lock_irqsave(&ds->irq_lock, irqflags);
|
||||
if (ks->debounce & DEBOUNCE_WAIT_IRQ) {
|
||||
ks->debounce = DEBOUNCE_UNKNOWN;
|
||||
if (ds->debounce_count++ == 0) {
|
||||
__pm_stay_awake(ds->ws);
|
||||
hrtimer_start(
|
||||
&ds->timer, ds->info->debounce_time,
|
||||
HRTIMER_MODE_REL);
|
||||
}
|
||||
if (ds->info->flags & GPIOEDF_PRINT_KEY_DEBOUNCE)
|
||||
pr_info("gpio_event_input_irq_handler: "
|
||||
"key %x-%x, %d (%d) start debounce\n",
|
||||
ds->info->type, key_entry->code,
|
||||
keymap_index, key_entry->gpio);
|
||||
} else {
|
||||
disable_irq_nosync(irq);
|
||||
ks->debounce = DEBOUNCE_UNSTABLE;
|
||||
}
|
||||
spin_unlock_irqrestore(&ds->irq_lock, irqflags);
|
||||
} else {
|
||||
pressed = gpio_get_value(key_entry->gpio) ^
|
||||
!(ds->info->flags & GPIOEDF_ACTIVE_HIGH);
|
||||
if (ds->info->flags & GPIOEDF_PRINT_KEYS)
|
||||
pr_info("gpio_event_input_irq_handler: key %x-%x, %d "
|
||||
"(%d) changed to %d\n",
|
||||
ds->info->type, key_entry->code, keymap_index,
|
||||
key_entry->gpio, pressed);
|
||||
input_event(ds->input_devs->dev[key_entry->dev], ds->info->type,
|
||||
key_entry->code, pressed);
|
||||
input_sync(ds->input_devs->dev[key_entry->dev]);
|
||||
}
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int gpio_event_input_request_irqs(struct gpio_input_state *ds)
|
||||
{
|
||||
int i;
|
||||
int err;
|
||||
unsigned int irq;
|
||||
unsigned long req_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
|
||||
|
||||
for (i = 0; i < ds->info->keymap_size; i++) {
|
||||
err = irq = gpio_to_irq(ds->info->keymap[i].gpio);
|
||||
if (err < 0)
|
||||
goto err_gpio_get_irq_num_failed;
|
||||
err = request_irq(irq, gpio_event_input_irq_handler,
|
||||
req_flags, "gpio_keys", &ds->key_state[i]);
|
||||
if (err) {
|
||||
pr_err("gpio_event_input_request_irqs: request_irq "
|
||||
"failed for input %d, irq %d\n",
|
||||
ds->info->keymap[i].gpio, irq);
|
||||
goto err_request_irq_failed;
|
||||
}
|
||||
if (ds->info->info.no_suspend) {
|
||||
err = enable_irq_wake(irq);
|
||||
if (err) {
|
||||
pr_err("gpio_event_input_request_irqs: "
|
||||
"enable_irq_wake failed for input %d, "
|
||||
"irq %d\n",
|
||||
ds->info->keymap[i].gpio, irq);
|
||||
goto err_enable_irq_wake_failed;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
for (i = ds->info->keymap_size - 1; i >= 0; i--) {
|
||||
irq = gpio_to_irq(ds->info->keymap[i].gpio);
|
||||
if (ds->info->info.no_suspend)
|
||||
disable_irq_wake(irq);
|
||||
err_enable_irq_wake_failed:
|
||||
free_irq(irq, &ds->key_state[i]);
|
||||
err_request_irq_failed:
|
||||
err_gpio_get_irq_num_failed:
|
||||
;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
int gpio_event_input_func(struct gpio_event_input_devs *input_devs,
|
||||
struct gpio_event_info *info, void **data, int func)
|
||||
{
|
||||
int ret;
|
||||
int i;
|
||||
unsigned long irqflags;
|
||||
struct gpio_event_input_info *di;
|
||||
struct gpio_input_state *ds = *data;
|
||||
char *wlname;
|
||||
|
||||
di = container_of(info, struct gpio_event_input_info, info);
|
||||
|
||||
if (func == GPIO_EVENT_FUNC_SUSPEND) {
|
||||
if (ds->use_irq)
|
||||
for (i = 0; i < di->keymap_size; i++)
|
||||
disable_irq(gpio_to_irq(di->keymap[i].gpio));
|
||||
hrtimer_cancel(&ds->timer);
|
||||
return 0;
|
||||
}
|
||||
if (func == GPIO_EVENT_FUNC_RESUME) {
|
||||
spin_lock_irqsave(&ds->irq_lock, irqflags);
|
||||
if (ds->use_irq)
|
||||
for (i = 0; i < di->keymap_size; i++)
|
||||
enable_irq(gpio_to_irq(di->keymap[i].gpio));
|
||||
hrtimer_start(&ds->timer, ktime_set(0, 0), HRTIMER_MODE_REL);
|
||||
spin_unlock_irqrestore(&ds->irq_lock, irqflags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (func == GPIO_EVENT_FUNC_INIT) {
|
||||
if (ktime_to_ns(di->poll_time) <= 0)
|
||||
di->poll_time = ktime_set(0, 20 * NSEC_PER_MSEC);
|
||||
|
||||
*data = ds = kzalloc(sizeof(*ds) + sizeof(ds->key_state[0]) *
|
||||
di->keymap_size, GFP_KERNEL);
|
||||
if (ds == NULL) {
|
||||
ret = -ENOMEM;
|
||||
pr_err("gpio_event_input_func: "
|
||||
"Failed to allocate private data\n");
|
||||
goto err_ds_alloc_failed;
|
||||
}
|
||||
ds->debounce_count = di->keymap_size;
|
||||
ds->input_devs = input_devs;
|
||||
ds->info = di;
|
||||
wlname = kasprintf(GFP_KERNEL, "gpio_input:%s%s",
|
||||
input_devs->dev[0]->name,
|
||||
(input_devs->count > 1) ? "..." : "");
|
||||
|
||||
ds->ws = wakeup_source_register(wlname);
|
||||
kfree(wlname);
|
||||
if (!ds->ws) {
|
||||
ret = -ENOMEM;
|
||||
pr_err("gpio_event_input_func: "
|
||||
"Failed to allocate wakeup source\n");
|
||||
goto err_ws_failed;
|
||||
}
|
||||
|
||||
spin_lock_init(&ds->irq_lock);
|
||||
|
||||
for (i = 0; i < di->keymap_size; i++) {
|
||||
int dev = di->keymap[i].dev;
|
||||
if (dev >= input_devs->count) {
|
||||
pr_err("gpio_event_input_func: bad device "
|
||||
"index %d >= %d for key code %d\n",
|
||||
dev, input_devs->count,
|
||||
di->keymap[i].code);
|
||||
ret = -EINVAL;
|
||||
goto err_bad_keymap;
|
||||
}
|
||||
input_set_capability(input_devs->dev[dev], di->type,
|
||||
di->keymap[i].code);
|
||||
ds->key_state[i].ds = ds;
|
||||
ds->key_state[i].debounce = DEBOUNCE_UNKNOWN;
|
||||
}
|
||||
|
||||
for (i = 0; i < di->keymap_size; i++) {
|
||||
ret = gpio_request(di->keymap[i].gpio, "gpio_kp_in");
|
||||
if (ret) {
|
||||
pr_err("gpio_event_input_func: gpio_request "
|
||||
"failed for %d\n", di->keymap[i].gpio);
|
||||
goto err_gpio_request_failed;
|
||||
}
|
||||
ret = gpio_direction_input(di->keymap[i].gpio);
|
||||
if (ret) {
|
||||
pr_err("gpio_event_input_func: "
|
||||
"gpio_direction_input failed for %d\n",
|
||||
di->keymap[i].gpio);
|
||||
goto err_gpio_configure_failed;
|
||||
}
|
||||
}
|
||||
|
||||
ret = gpio_event_input_request_irqs(ds);
|
||||
|
||||
spin_lock_irqsave(&ds->irq_lock, irqflags);
|
||||
ds->use_irq = ret == 0;
|
||||
|
||||
pr_info("GPIO Input Driver: Start gpio inputs for %s%s in %s "
|
||||
"mode\n", input_devs->dev[0]->name,
|
||||
(input_devs->count > 1) ? "..." : "",
|
||||
ret == 0 ? "interrupt" : "polling");
|
||||
|
||||
hrtimer_init(&ds->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||
ds->timer.function = gpio_event_input_timer_func;
|
||||
hrtimer_start(&ds->timer, ktime_set(0, 0), HRTIMER_MODE_REL);
|
||||
spin_unlock_irqrestore(&ds->irq_lock, irqflags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
spin_lock_irqsave(&ds->irq_lock, irqflags);
|
||||
hrtimer_cancel(&ds->timer);
|
||||
if (ds->use_irq) {
|
||||
for (i = di->keymap_size - 1; i >= 0; i--) {
|
||||
int irq = gpio_to_irq(di->keymap[i].gpio);
|
||||
if (ds->info->info.no_suspend)
|
||||
disable_irq_wake(irq);
|
||||
free_irq(irq, &ds->key_state[i]);
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&ds->irq_lock, irqflags);
|
||||
|
||||
for (i = di->keymap_size - 1; i >= 0; i--) {
|
||||
err_gpio_configure_failed:
|
||||
gpio_free(di->keymap[i].gpio);
|
||||
err_gpio_request_failed:
|
||||
;
|
||||
}
|
||||
err_bad_keymap:
|
||||
wakeup_source_unregister(ds->ws);
|
||||
err_ws_failed:
|
||||
kfree(ds);
|
||||
err_ds_alloc_failed:
|
||||
return ret;
|
||||
}
|
||||
441
drivers/input/misc/gpio_matrix.c
Normal file
441
drivers/input/misc/gpio_matrix.c
Normal file
|
|
@ -0,0 +1,441 @@
|
|||
/* drivers/input/misc/gpio_matrix.c
|
||||
*
|
||||
* Copyright (C) 2007 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio_event.h>
|
||||
#include <linux/hrtimer.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/wakelock.h>
|
||||
|
||||
struct gpio_kp {
|
||||
struct gpio_event_input_devs *input_devs;
|
||||
struct gpio_event_matrix_info *keypad_info;
|
||||
struct hrtimer timer;
|
||||
struct wake_lock wake_lock;
|
||||
int current_output;
|
||||
unsigned int use_irq:1;
|
||||
unsigned int key_state_changed:1;
|
||||
unsigned int last_key_state_changed:1;
|
||||
unsigned int some_keys_pressed:2;
|
||||
unsigned int disabled_irq:1;
|
||||
unsigned long keys_pressed[0];
|
||||
};
|
||||
|
||||
static void clear_phantom_key(struct gpio_kp *kp, int out, int in)
|
||||
{
|
||||
struct gpio_event_matrix_info *mi = kp->keypad_info;
|
||||
int key_index = out * mi->ninputs + in;
|
||||
unsigned short keyentry = mi->keymap[key_index];
|
||||
unsigned short keycode = keyentry & MATRIX_KEY_MASK;
|
||||
unsigned short dev = keyentry >> MATRIX_CODE_BITS;
|
||||
|
||||
if (!test_bit(keycode, kp->input_devs->dev[dev]->key)) {
|
||||
if (mi->flags & GPIOKPF_PRINT_PHANTOM_KEYS)
|
||||
pr_info("gpiomatrix: phantom key %x, %d-%d (%d-%d) "
|
||||
"cleared\n", keycode, out, in,
|
||||
mi->output_gpios[out], mi->input_gpios[in]);
|
||||
__clear_bit(key_index, kp->keys_pressed);
|
||||
} else {
|
||||
if (mi->flags & GPIOKPF_PRINT_PHANTOM_KEYS)
|
||||
pr_info("gpiomatrix: phantom key %x, %d-%d (%d-%d) "
|
||||
"not cleared\n", keycode, out, in,
|
||||
mi->output_gpios[out], mi->input_gpios[in]);
|
||||
}
|
||||
}
|
||||
|
||||
static int restore_keys_for_input(struct gpio_kp *kp, int out, int in)
|
||||
{
|
||||
int rv = 0;
|
||||
int key_index;
|
||||
|
||||
key_index = out * kp->keypad_info->ninputs + in;
|
||||
while (out < kp->keypad_info->noutputs) {
|
||||
if (test_bit(key_index, kp->keys_pressed)) {
|
||||
rv = 1;
|
||||
clear_phantom_key(kp, out, in);
|
||||
}
|
||||
key_index += kp->keypad_info->ninputs;
|
||||
out++;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void remove_phantom_keys(struct gpio_kp *kp)
|
||||
{
|
||||
int out, in, inp;
|
||||
int key_index;
|
||||
|
||||
if (kp->some_keys_pressed < 3)
|
||||
return;
|
||||
|
||||
for (out = 0; out < kp->keypad_info->noutputs; out++) {
|
||||
inp = -1;
|
||||
key_index = out * kp->keypad_info->ninputs;
|
||||
for (in = 0; in < kp->keypad_info->ninputs; in++, key_index++) {
|
||||
if (test_bit(key_index, kp->keys_pressed)) {
|
||||
if (inp == -1) {
|
||||
inp = in;
|
||||
continue;
|
||||
}
|
||||
if (inp >= 0) {
|
||||
if (!restore_keys_for_input(kp, out + 1,
|
||||
inp))
|
||||
break;
|
||||
clear_phantom_key(kp, out, inp);
|
||||
inp = -2;
|
||||
}
|
||||
restore_keys_for_input(kp, out, in);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void report_key(struct gpio_kp *kp, int key_index, int out, int in)
|
||||
{
|
||||
struct gpio_event_matrix_info *mi = kp->keypad_info;
|
||||
int pressed = test_bit(key_index, kp->keys_pressed);
|
||||
unsigned short keyentry = mi->keymap[key_index];
|
||||
unsigned short keycode = keyentry & MATRIX_KEY_MASK;
|
||||
unsigned short dev = keyentry >> MATRIX_CODE_BITS;
|
||||
|
||||
if (pressed != test_bit(keycode, kp->input_devs->dev[dev]->key)) {
|
||||
if (keycode == KEY_RESERVED) {
|
||||
if (mi->flags & GPIOKPF_PRINT_UNMAPPED_KEYS)
|
||||
pr_info("gpiomatrix: unmapped key, %d-%d "
|
||||
"(%d-%d) changed to %d\n",
|
||||
out, in, mi->output_gpios[out],
|
||||
mi->input_gpios[in], pressed);
|
||||
} else {
|
||||
if (mi->flags & GPIOKPF_PRINT_MAPPED_KEYS)
|
||||
pr_info("gpiomatrix: key %x, %d-%d (%d-%d) "
|
||||
"changed to %d\n", keycode,
|
||||
out, in, mi->output_gpios[out],
|
||||
mi->input_gpios[in], pressed);
|
||||
input_report_key(kp->input_devs->dev[dev], keycode, pressed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void report_sync(struct gpio_kp *kp)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < kp->input_devs->count; i++)
|
||||
input_sync(kp->input_devs->dev[i]);
|
||||
}
|
||||
|
||||
static enum hrtimer_restart gpio_keypad_timer_func(struct hrtimer *timer)
|
||||
{
|
||||
int out, in;
|
||||
int key_index;
|
||||
int gpio;
|
||||
struct gpio_kp *kp = container_of(timer, struct gpio_kp, timer);
|
||||
struct gpio_event_matrix_info *mi = kp->keypad_info;
|
||||
unsigned gpio_keypad_flags = mi->flags;
|
||||
unsigned polarity = !!(gpio_keypad_flags & GPIOKPF_ACTIVE_HIGH);
|
||||
|
||||
out = kp->current_output;
|
||||
if (out == mi->noutputs) {
|
||||
out = 0;
|
||||
kp->last_key_state_changed = kp->key_state_changed;
|
||||
kp->key_state_changed = 0;
|
||||
kp->some_keys_pressed = 0;
|
||||
} else {
|
||||
key_index = out * mi->ninputs;
|
||||
for (in = 0; in < mi->ninputs; in++, key_index++) {
|
||||
gpio = mi->input_gpios[in];
|
||||
if (gpio_get_value(gpio) ^ !polarity) {
|
||||
if (kp->some_keys_pressed < 3)
|
||||
kp->some_keys_pressed++;
|
||||
kp->key_state_changed |= !__test_and_set_bit(
|
||||
key_index, kp->keys_pressed);
|
||||
} else
|
||||
kp->key_state_changed |= __test_and_clear_bit(
|
||||
key_index, kp->keys_pressed);
|
||||
}
|
||||
gpio = mi->output_gpios[out];
|
||||
if (gpio_keypad_flags & GPIOKPF_DRIVE_INACTIVE)
|
||||
gpio_set_value(gpio, !polarity);
|
||||
else
|
||||
gpio_direction_input(gpio);
|
||||
out++;
|
||||
}
|
||||
kp->current_output = out;
|
||||
if (out < mi->noutputs) {
|
||||
gpio = mi->output_gpios[out];
|
||||
if (gpio_keypad_flags & GPIOKPF_DRIVE_INACTIVE)
|
||||
gpio_set_value(gpio, polarity);
|
||||
else
|
||||
gpio_direction_output(gpio, polarity);
|
||||
hrtimer_start(timer, mi->settle_time, HRTIMER_MODE_REL);
|
||||
return HRTIMER_NORESTART;
|
||||
}
|
||||
if (gpio_keypad_flags & GPIOKPF_DEBOUNCE) {
|
||||
if (kp->key_state_changed) {
|
||||
hrtimer_start(&kp->timer, mi->debounce_delay,
|
||||
HRTIMER_MODE_REL);
|
||||
return HRTIMER_NORESTART;
|
||||
}
|
||||
kp->key_state_changed = kp->last_key_state_changed;
|
||||
}
|
||||
if (kp->key_state_changed) {
|
||||
if (gpio_keypad_flags & GPIOKPF_REMOVE_SOME_PHANTOM_KEYS)
|
||||
remove_phantom_keys(kp);
|
||||
key_index = 0;
|
||||
for (out = 0; out < mi->noutputs; out++)
|
||||
for (in = 0; in < mi->ninputs; in++, key_index++)
|
||||
report_key(kp, key_index, out, in);
|
||||
report_sync(kp);
|
||||
}
|
||||
if (!kp->use_irq || kp->some_keys_pressed) {
|
||||
hrtimer_start(timer, mi->poll_time, HRTIMER_MODE_REL);
|
||||
return HRTIMER_NORESTART;
|
||||
}
|
||||
|
||||
/* No keys are pressed, reenable interrupt */
|
||||
for (out = 0; out < mi->noutputs; out++) {
|
||||
if (gpio_keypad_flags & GPIOKPF_DRIVE_INACTIVE)
|
||||
gpio_set_value(mi->output_gpios[out], polarity);
|
||||
else
|
||||
gpio_direction_output(mi->output_gpios[out], polarity);
|
||||
}
|
||||
for (in = 0; in < mi->ninputs; in++)
|
||||
enable_irq(gpio_to_irq(mi->input_gpios[in]));
|
||||
wake_unlock(&kp->wake_lock);
|
||||
return HRTIMER_NORESTART;
|
||||
}
|
||||
|
||||
static irqreturn_t gpio_keypad_irq_handler(int irq_in, void *dev_id)
|
||||
{
|
||||
int i;
|
||||
struct gpio_kp *kp = dev_id;
|
||||
struct gpio_event_matrix_info *mi = kp->keypad_info;
|
||||
unsigned gpio_keypad_flags = mi->flags;
|
||||
|
||||
if (!kp->use_irq) {
|
||||
/* ignore interrupt while registering the handler */
|
||||
kp->disabled_irq = 1;
|
||||
disable_irq_nosync(irq_in);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
for (i = 0; i < mi->ninputs; i++)
|
||||
disable_irq_nosync(gpio_to_irq(mi->input_gpios[i]));
|
||||
for (i = 0; i < mi->noutputs; i++) {
|
||||
if (gpio_keypad_flags & GPIOKPF_DRIVE_INACTIVE)
|
||||
gpio_set_value(mi->output_gpios[i],
|
||||
!(gpio_keypad_flags & GPIOKPF_ACTIVE_HIGH));
|
||||
else
|
||||
gpio_direction_input(mi->output_gpios[i]);
|
||||
}
|
||||
wake_lock(&kp->wake_lock);
|
||||
hrtimer_start(&kp->timer, ktime_set(0, 0), HRTIMER_MODE_REL);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int gpio_keypad_request_irqs(struct gpio_kp *kp)
|
||||
{
|
||||
int i;
|
||||
int err;
|
||||
unsigned int irq;
|
||||
unsigned long request_flags;
|
||||
struct gpio_event_matrix_info *mi = kp->keypad_info;
|
||||
|
||||
switch (mi->flags & (GPIOKPF_ACTIVE_HIGH|GPIOKPF_LEVEL_TRIGGERED_IRQ)) {
|
||||
default:
|
||||
request_flags = IRQF_TRIGGER_FALLING;
|
||||
break;
|
||||
case GPIOKPF_ACTIVE_HIGH:
|
||||
request_flags = IRQF_TRIGGER_RISING;
|
||||
break;
|
||||
case GPIOKPF_LEVEL_TRIGGERED_IRQ:
|
||||
request_flags = IRQF_TRIGGER_LOW;
|
||||
break;
|
||||
case GPIOKPF_LEVEL_TRIGGERED_IRQ | GPIOKPF_ACTIVE_HIGH:
|
||||
request_flags = IRQF_TRIGGER_HIGH;
|
||||
break;
|
||||
}
|
||||
|
||||
for (i = 0; i < mi->ninputs; i++) {
|
||||
err = irq = gpio_to_irq(mi->input_gpios[i]);
|
||||
if (err < 0)
|
||||
goto err_gpio_get_irq_num_failed;
|
||||
err = request_irq(irq, gpio_keypad_irq_handler, request_flags,
|
||||
"gpio_kp", kp);
|
||||
if (err) {
|
||||
pr_err("gpiomatrix: request_irq failed for input %d, "
|
||||
"irq %d\n", mi->input_gpios[i], irq);
|
||||
goto err_request_irq_failed;
|
||||
}
|
||||
err = enable_irq_wake(irq);
|
||||
if (err) {
|
||||
pr_err("gpiomatrix: set_irq_wake failed for input %d, "
|
||||
"irq %d\n", mi->input_gpios[i], irq);
|
||||
}
|
||||
disable_irq(irq);
|
||||
if (kp->disabled_irq) {
|
||||
kp->disabled_irq = 0;
|
||||
enable_irq(irq);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
for (i = mi->noutputs - 1; i >= 0; i--) {
|
||||
free_irq(gpio_to_irq(mi->input_gpios[i]), kp);
|
||||
err_request_irq_failed:
|
||||
err_gpio_get_irq_num_failed:
|
||||
;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
int gpio_event_matrix_func(struct gpio_event_input_devs *input_devs,
|
||||
struct gpio_event_info *info, void **data, int func)
|
||||
{
|
||||
int i;
|
||||
int err;
|
||||
int key_count;
|
||||
struct gpio_kp *kp;
|
||||
struct gpio_event_matrix_info *mi;
|
||||
|
||||
mi = container_of(info, struct gpio_event_matrix_info, info);
|
||||
if (func == GPIO_EVENT_FUNC_SUSPEND || func == GPIO_EVENT_FUNC_RESUME) {
|
||||
/* TODO: disable scanning */
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (func == GPIO_EVENT_FUNC_INIT) {
|
||||
if (mi->keymap == NULL ||
|
||||
mi->input_gpios == NULL ||
|
||||
mi->output_gpios == NULL) {
|
||||
err = -ENODEV;
|
||||
pr_err("gpiomatrix: Incomplete pdata\n");
|
||||
goto err_invalid_platform_data;
|
||||
}
|
||||
key_count = mi->ninputs * mi->noutputs;
|
||||
|
||||
*data = kp = kzalloc(sizeof(*kp) + sizeof(kp->keys_pressed[0]) *
|
||||
BITS_TO_LONGS(key_count), GFP_KERNEL);
|
||||
if (kp == NULL) {
|
||||
err = -ENOMEM;
|
||||
pr_err("gpiomatrix: Failed to allocate private data\n");
|
||||
goto err_kp_alloc_failed;
|
||||
}
|
||||
kp->input_devs = input_devs;
|
||||
kp->keypad_info = mi;
|
||||
for (i = 0; i < key_count; i++) {
|
||||
unsigned short keyentry = mi->keymap[i];
|
||||
unsigned short keycode = keyentry & MATRIX_KEY_MASK;
|
||||
unsigned short dev = keyentry >> MATRIX_CODE_BITS;
|
||||
if (dev >= input_devs->count) {
|
||||
pr_err("gpiomatrix: bad device index %d >= "
|
||||
"%d for key code %d\n",
|
||||
dev, input_devs->count, keycode);
|
||||
err = -EINVAL;
|
||||
goto err_bad_keymap;
|
||||
}
|
||||
if (keycode && keycode <= KEY_MAX)
|
||||
input_set_capability(input_devs->dev[dev],
|
||||
EV_KEY, keycode);
|
||||
}
|
||||
|
||||
for (i = 0; i < mi->noutputs; i++) {
|
||||
err = gpio_request(mi->output_gpios[i], "gpio_kp_out");
|
||||
if (err) {
|
||||
pr_err("gpiomatrix: gpio_request failed for "
|
||||
"output %d\n", mi->output_gpios[i]);
|
||||
goto err_request_output_gpio_failed;
|
||||
}
|
||||
if (gpio_cansleep(mi->output_gpios[i])) {
|
||||
pr_err("gpiomatrix: unsupported output gpio %d,"
|
||||
" can sleep\n", mi->output_gpios[i]);
|
||||
err = -EINVAL;
|
||||
goto err_output_gpio_configure_failed;
|
||||
}
|
||||
if (mi->flags & GPIOKPF_DRIVE_INACTIVE)
|
||||
err = gpio_direction_output(mi->output_gpios[i],
|
||||
!(mi->flags & GPIOKPF_ACTIVE_HIGH));
|
||||
else
|
||||
err = gpio_direction_input(mi->output_gpios[i]);
|
||||
if (err) {
|
||||
pr_err("gpiomatrix: gpio_configure failed for "
|
||||
"output %d\n", mi->output_gpios[i]);
|
||||
goto err_output_gpio_configure_failed;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < mi->ninputs; i++) {
|
||||
err = gpio_request(mi->input_gpios[i], "gpio_kp_in");
|
||||
if (err) {
|
||||
pr_err("gpiomatrix: gpio_request failed for "
|
||||
"input %d\n", mi->input_gpios[i]);
|
||||
goto err_request_input_gpio_failed;
|
||||
}
|
||||
err = gpio_direction_input(mi->input_gpios[i]);
|
||||
if (err) {
|
||||
pr_err("gpiomatrix: gpio_direction_input failed"
|
||||
" for input %d\n", mi->input_gpios[i]);
|
||||
goto err_gpio_direction_input_failed;
|
||||
}
|
||||
}
|
||||
kp->current_output = mi->noutputs;
|
||||
kp->key_state_changed = 1;
|
||||
|
||||
hrtimer_init(&kp->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||
kp->timer.function = gpio_keypad_timer_func;
|
||||
wake_lock_init(&kp->wake_lock, WAKE_LOCK_SUSPEND, "gpio_kp");
|
||||
err = gpio_keypad_request_irqs(kp);
|
||||
kp->use_irq = err == 0;
|
||||
|
||||
pr_info("GPIO Matrix Keypad Driver: Start keypad matrix for "
|
||||
"%s%s in %s mode\n", input_devs->dev[0]->name,
|
||||
(input_devs->count > 1) ? "..." : "",
|
||||
kp->use_irq ? "interrupt" : "polling");
|
||||
|
||||
if (kp->use_irq)
|
||||
wake_lock(&kp->wake_lock);
|
||||
hrtimer_start(&kp->timer, ktime_set(0, 0), HRTIMER_MODE_REL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
err = 0;
|
||||
kp = *data;
|
||||
|
||||
if (kp->use_irq)
|
||||
for (i = mi->noutputs - 1; i >= 0; i--)
|
||||
free_irq(gpio_to_irq(mi->input_gpios[i]), kp);
|
||||
|
||||
hrtimer_cancel(&kp->timer);
|
||||
wake_lock_destroy(&kp->wake_lock);
|
||||
for (i = mi->noutputs - 1; i >= 0; i--) {
|
||||
err_gpio_direction_input_failed:
|
||||
gpio_free(mi->input_gpios[i]);
|
||||
err_request_input_gpio_failed:
|
||||
;
|
||||
}
|
||||
for (i = mi->noutputs - 1; i >= 0; i--) {
|
||||
err_output_gpio_configure_failed:
|
||||
gpio_free(mi->output_gpios[i]);
|
||||
err_request_output_gpio_failed:
|
||||
;
|
||||
}
|
||||
err_bad_keymap:
|
||||
kfree(kp);
|
||||
err_kp_alloc_failed:
|
||||
err_invalid_platform_data:
|
||||
return err;
|
||||
}
|
||||
97
drivers/input/misc/gpio_output.c
Normal file
97
drivers/input/misc/gpio_output.c
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/* drivers/input/misc/gpio_output.c
|
||||
*
|
||||
* Copyright (C) 2007 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio_event.h>
|
||||
|
||||
int gpio_event_output_event(
|
||||
struct gpio_event_input_devs *input_devs, struct gpio_event_info *info,
|
||||
void **data, unsigned int dev, unsigned int type,
|
||||
unsigned int code, int value)
|
||||
{
|
||||
int i;
|
||||
struct gpio_event_output_info *oi;
|
||||
oi = container_of(info, struct gpio_event_output_info, info);
|
||||
if (type != oi->type)
|
||||
return 0;
|
||||
if (!(oi->flags & GPIOEDF_ACTIVE_HIGH))
|
||||
value = !value;
|
||||
for (i = 0; i < oi->keymap_size; i++)
|
||||
if (dev == oi->keymap[i].dev && code == oi->keymap[i].code)
|
||||
gpio_set_value(oi->keymap[i].gpio, value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gpio_event_output_func(
|
||||
struct gpio_event_input_devs *input_devs, struct gpio_event_info *info,
|
||||
void **data, int func)
|
||||
{
|
||||
int ret;
|
||||
int i;
|
||||
struct gpio_event_output_info *oi;
|
||||
oi = container_of(info, struct gpio_event_output_info, info);
|
||||
|
||||
if (func == GPIO_EVENT_FUNC_SUSPEND || func == GPIO_EVENT_FUNC_RESUME)
|
||||
return 0;
|
||||
|
||||
if (func == GPIO_EVENT_FUNC_INIT) {
|
||||
int output_level = !(oi->flags & GPIOEDF_ACTIVE_HIGH);
|
||||
|
||||
for (i = 0; i < oi->keymap_size; i++) {
|
||||
int dev = oi->keymap[i].dev;
|
||||
if (dev >= input_devs->count) {
|
||||
pr_err("gpio_event_output_func: bad device "
|
||||
"index %d >= %d for key code %d\n",
|
||||
dev, input_devs->count,
|
||||
oi->keymap[i].code);
|
||||
ret = -EINVAL;
|
||||
goto err_bad_keymap;
|
||||
}
|
||||
input_set_capability(input_devs->dev[dev], oi->type,
|
||||
oi->keymap[i].code);
|
||||
}
|
||||
|
||||
for (i = 0; i < oi->keymap_size; i++) {
|
||||
ret = gpio_request(oi->keymap[i].gpio,
|
||||
"gpio_event_output");
|
||||
if (ret) {
|
||||
pr_err("gpio_event_output_func: gpio_request "
|
||||
"failed for %d\n", oi->keymap[i].gpio);
|
||||
goto err_gpio_request_failed;
|
||||
}
|
||||
ret = gpio_direction_output(oi->keymap[i].gpio,
|
||||
output_level);
|
||||
if (ret) {
|
||||
pr_err("gpio_event_output_func: "
|
||||
"gpio_direction_output failed for %d\n",
|
||||
oi->keymap[i].gpio);
|
||||
goto err_gpio_direction_output_failed;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
for (i = oi->keymap_size - 1; i >= 0; i--) {
|
||||
err_gpio_direction_output_failed:
|
||||
gpio_free(oi->keymap[i].gpio);
|
||||
err_gpio_request_failed:
|
||||
;
|
||||
}
|
||||
err_bad_keymap:
|
||||
return ret;
|
||||
}
|
||||
|
||||
391
drivers/input/misc/keychord.c
Normal file
391
drivers/input/misc/keychord.c
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
/*
|
||||
* drivers/input/misc/keychord.c
|
||||
*
|
||||
* Copyright (C) 2008 Google, Inc.
|
||||
* Author: Mike Lockwood <lockwood@android.com>
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/poll.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/keychord.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
#define KEYCHORD_NAME "keychord"
|
||||
#define BUFFER_SIZE 16
|
||||
|
||||
MODULE_AUTHOR("Mike Lockwood <lockwood@android.com>");
|
||||
MODULE_DESCRIPTION("Key chord input driver");
|
||||
MODULE_SUPPORTED_DEVICE("keychord");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
#define NEXT_KEYCHORD(kc) ((struct input_keychord *) \
|
||||
((char *)kc + sizeof(struct input_keychord) + \
|
||||
kc->count * sizeof(kc->keycodes[0])))
|
||||
|
||||
struct keychord_device {
|
||||
struct input_handler input_handler;
|
||||
int registered;
|
||||
|
||||
/* list of keychords to monitor */
|
||||
struct input_keychord *keychords;
|
||||
int keychord_count;
|
||||
|
||||
/* bitmask of keys contained in our keychords */
|
||||
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
|
||||
/* current state of the keys */
|
||||
unsigned long keystate[BITS_TO_LONGS(KEY_CNT)];
|
||||
/* number of keys that are currently pressed */
|
||||
int key_down;
|
||||
|
||||
/* second input_device_id is needed for null termination */
|
||||
struct input_device_id device_ids[2];
|
||||
|
||||
spinlock_t lock;
|
||||
wait_queue_head_t waitq;
|
||||
unsigned char head;
|
||||
unsigned char tail;
|
||||
__u16 buff[BUFFER_SIZE];
|
||||
};
|
||||
|
||||
static int check_keychord(struct keychord_device *kdev,
|
||||
struct input_keychord *keychord)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (keychord->count != kdev->key_down)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < keychord->count; i++) {
|
||||
if (!test_bit(keychord->keycodes[i], kdev->keystate))
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* we have a match */
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void keychord_event(struct input_handle *handle, unsigned int type,
|
||||
unsigned int code, int value)
|
||||
{
|
||||
struct keychord_device *kdev = handle->private;
|
||||
struct input_keychord *keychord;
|
||||
unsigned long flags;
|
||||
int i, got_chord = 0;
|
||||
|
||||
if (type != EV_KEY || code >= KEY_MAX)
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&kdev->lock, flags);
|
||||
/* do nothing if key state did not change */
|
||||
if (!test_bit(code, kdev->keystate) == !value)
|
||||
goto done;
|
||||
__change_bit(code, kdev->keystate);
|
||||
if (value)
|
||||
kdev->key_down++;
|
||||
else
|
||||
kdev->key_down--;
|
||||
|
||||
/* don't notify on key up */
|
||||
if (!value)
|
||||
goto done;
|
||||
/* ignore this event if it is not one of the keys we are monitoring */
|
||||
if (!test_bit(code, kdev->keybit))
|
||||
goto done;
|
||||
|
||||
keychord = kdev->keychords;
|
||||
if (!keychord)
|
||||
goto done;
|
||||
|
||||
/* check to see if the keyboard state matches any keychords */
|
||||
for (i = 0; i < kdev->keychord_count; i++) {
|
||||
if (check_keychord(kdev, keychord)) {
|
||||
kdev->buff[kdev->head] = keychord->id;
|
||||
kdev->head = (kdev->head + 1) % BUFFER_SIZE;
|
||||
got_chord = 1;
|
||||
break;
|
||||
}
|
||||
/* skip to next keychord */
|
||||
keychord = NEXT_KEYCHORD(keychord);
|
||||
}
|
||||
|
||||
done:
|
||||
spin_unlock_irqrestore(&kdev->lock, flags);
|
||||
|
||||
if (got_chord) {
|
||||
pr_info("keychord: got keychord id %d. Any tasks: %d\n",
|
||||
keychord->id,
|
||||
!list_empty_careful(&kdev->waitq.task_list));
|
||||
wake_up_interruptible(&kdev->waitq);
|
||||
}
|
||||
}
|
||||
|
||||
static int keychord_connect(struct input_handler *handler,
|
||||
struct input_dev *dev,
|
||||
const struct input_device_id *id)
|
||||
{
|
||||
int i, ret;
|
||||
struct input_handle *handle;
|
||||
struct keychord_device *kdev =
|
||||
container_of(handler, struct keychord_device, input_handler);
|
||||
|
||||
/*
|
||||
* ignore this input device if it does not contain any keycodes
|
||||
* that we are monitoring
|
||||
*/
|
||||
for (i = 0; i < KEY_MAX; i++) {
|
||||
if (test_bit(i, kdev->keybit) && test_bit(i, dev->keybit))
|
||||
break;
|
||||
}
|
||||
if (i == KEY_MAX)
|
||||
return -ENODEV;
|
||||
|
||||
handle = kzalloc(sizeof(*handle), GFP_KERNEL);
|
||||
if (!handle)
|
||||
return -ENOMEM;
|
||||
|
||||
handle->dev = dev;
|
||||
handle->handler = handler;
|
||||
handle->name = KEYCHORD_NAME;
|
||||
handle->private = kdev;
|
||||
|
||||
ret = input_register_handle(handle);
|
||||
if (ret)
|
||||
goto err_input_register_handle;
|
||||
|
||||
ret = input_open_device(handle);
|
||||
if (ret)
|
||||
goto err_input_open_device;
|
||||
|
||||
pr_info("keychord: using input dev %s for fevent\n", dev->name);
|
||||
|
||||
return 0;
|
||||
|
||||
err_input_open_device:
|
||||
input_unregister_handle(handle);
|
||||
err_input_register_handle:
|
||||
kfree(handle);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void keychord_disconnect(struct input_handle *handle)
|
||||
{
|
||||
input_close_device(handle);
|
||||
input_unregister_handle(handle);
|
||||
kfree(handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* keychord_read is used to read keychord events from the driver
|
||||
*/
|
||||
static ssize_t keychord_read(struct file *file, char __user *buffer,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct keychord_device *kdev = file->private_data;
|
||||
__u16 id;
|
||||
int retval;
|
||||
unsigned long flags;
|
||||
|
||||
if (count < sizeof(id))
|
||||
return -EINVAL;
|
||||
count = sizeof(id);
|
||||
|
||||
if (kdev->head == kdev->tail && (file->f_flags & O_NONBLOCK))
|
||||
return -EAGAIN;
|
||||
|
||||
retval = wait_event_interruptible(kdev->waitq,
|
||||
kdev->head != kdev->tail);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
spin_lock_irqsave(&kdev->lock, flags);
|
||||
/* pop a keychord ID off the queue */
|
||||
id = kdev->buff[kdev->tail];
|
||||
kdev->tail = (kdev->tail + 1) % BUFFER_SIZE;
|
||||
spin_unlock_irqrestore(&kdev->lock, flags);
|
||||
|
||||
if (copy_to_user(buffer, &id, count))
|
||||
return -EFAULT;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
* keychord_write is used to configure the driver
|
||||
*/
|
||||
static ssize_t keychord_write(struct file *file, const char __user *buffer,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct keychord_device *kdev = file->private_data;
|
||||
struct input_keychord *keychords = 0;
|
||||
struct input_keychord *keychord, *next, *end;
|
||||
int ret, i, key;
|
||||
unsigned long flags;
|
||||
|
||||
if (count < sizeof(struct input_keychord))
|
||||
return -EINVAL;
|
||||
keychords = kzalloc(count, GFP_KERNEL);
|
||||
if (!keychords)
|
||||
return -ENOMEM;
|
||||
|
||||
/* read list of keychords from userspace */
|
||||
if (copy_from_user(keychords, buffer, count)) {
|
||||
kfree(keychords);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
/* unregister handler before changing configuration */
|
||||
if (kdev->registered) {
|
||||
input_unregister_handler(&kdev->input_handler);
|
||||
kdev->registered = 0;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&kdev->lock, flags);
|
||||
/* clear any existing configuration */
|
||||
kfree(kdev->keychords);
|
||||
kdev->keychords = 0;
|
||||
kdev->keychord_count = 0;
|
||||
kdev->key_down = 0;
|
||||
memset(kdev->keybit, 0, sizeof(kdev->keybit));
|
||||
memset(kdev->keystate, 0, sizeof(kdev->keystate));
|
||||
kdev->head = kdev->tail = 0;
|
||||
|
||||
keychord = keychords;
|
||||
end = (struct input_keychord *)((char *)keychord + count);
|
||||
|
||||
while (keychord < end) {
|
||||
next = NEXT_KEYCHORD(keychord);
|
||||
if (keychord->count <= 0 || next > end) {
|
||||
pr_err("keychord: invalid keycode count %d\n",
|
||||
keychord->count);
|
||||
goto err_unlock_return;
|
||||
}
|
||||
if (keychord->version != KEYCHORD_VERSION) {
|
||||
pr_err("keychord: unsupported version %d\n",
|
||||
keychord->version);
|
||||
goto err_unlock_return;
|
||||
}
|
||||
|
||||
/* keep track of the keys we are monitoring in keybit */
|
||||
for (i = 0; i < keychord->count; i++) {
|
||||
key = keychord->keycodes[i];
|
||||
if (key < 0 || key >= KEY_CNT) {
|
||||
pr_err("keychord: keycode %d out of range\n",
|
||||
key);
|
||||
goto err_unlock_return;
|
||||
}
|
||||
__set_bit(key, kdev->keybit);
|
||||
}
|
||||
|
||||
kdev->keychord_count++;
|
||||
keychord = next;
|
||||
}
|
||||
|
||||
kdev->keychords = keychords;
|
||||
spin_unlock_irqrestore(&kdev->lock, flags);
|
||||
|
||||
ret = input_register_handler(&kdev->input_handler);
|
||||
if (ret) {
|
||||
kfree(keychords);
|
||||
kdev->keychords = 0;
|
||||
return ret;
|
||||
}
|
||||
kdev->registered = 1;
|
||||
|
||||
return count;
|
||||
|
||||
err_unlock_return:
|
||||
spin_unlock_irqrestore(&kdev->lock, flags);
|
||||
kfree(keychords);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static unsigned int keychord_poll(struct file *file, poll_table *wait)
|
||||
{
|
||||
struct keychord_device *kdev = file->private_data;
|
||||
|
||||
poll_wait(file, &kdev->waitq, wait);
|
||||
|
||||
if (kdev->head != kdev->tail)
|
||||
return POLLIN | POLLRDNORM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int keychord_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct keychord_device *kdev;
|
||||
|
||||
kdev = kzalloc(sizeof(struct keychord_device), GFP_KERNEL);
|
||||
if (!kdev)
|
||||
return -ENOMEM;
|
||||
|
||||
spin_lock_init(&kdev->lock);
|
||||
init_waitqueue_head(&kdev->waitq);
|
||||
|
||||
kdev->input_handler.event = keychord_event;
|
||||
kdev->input_handler.connect = keychord_connect;
|
||||
kdev->input_handler.disconnect = keychord_disconnect;
|
||||
kdev->input_handler.name = KEYCHORD_NAME;
|
||||
kdev->input_handler.id_table = kdev->device_ids;
|
||||
|
||||
kdev->device_ids[0].flags = INPUT_DEVICE_ID_MATCH_EVBIT;
|
||||
__set_bit(EV_KEY, kdev->device_ids[0].evbit);
|
||||
|
||||
file->private_data = kdev;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int keychord_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct keychord_device *kdev = file->private_data;
|
||||
|
||||
if (kdev->registered)
|
||||
input_unregister_handler(&kdev->input_handler);
|
||||
kfree(kdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations keychord_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = keychord_open,
|
||||
.release = keychord_release,
|
||||
.read = keychord_read,
|
||||
.write = keychord_write,
|
||||
.poll = keychord_poll,
|
||||
};
|
||||
|
||||
static struct miscdevice keychord_misc = {
|
||||
.fops = &keychord_fops,
|
||||
.name = KEYCHORD_NAME,
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
};
|
||||
|
||||
static int __init keychord_init(void)
|
||||
{
|
||||
return misc_register(&keychord_misc);
|
||||
}
|
||||
|
||||
static void __exit keychord_exit(void)
|
||||
{
|
||||
misc_deregister(&keychord_misc);
|
||||
}
|
||||
|
||||
module_init(keychord_init);
|
||||
module_exit(keychord_exit);
|
||||
|
|
@ -412,6 +412,10 @@ config TI_DAC7512
|
|||
This driver can also be built as a module. If so, the module
|
||||
will be called ti_dac7512.
|
||||
|
||||
config UID_STAT
|
||||
bool "UID based statistics tracking exported to /proc/uid_stat"
|
||||
default n
|
||||
|
||||
config VMWARE_BALLOON
|
||||
tristate "VMware Balloon Driver"
|
||||
depends on VMWARE_VMCI && X86 && HYPERVISOR_GUEST
|
||||
|
|
@ -525,6 +529,12 @@ config VEXPRESS_SYSCFG
|
|||
bus. System Configuration interface is one of the possible means
|
||||
of generating transactions on this bus.
|
||||
|
||||
config UID_CPUTIME
|
||||
bool "Per-UID cpu time statistics"
|
||||
depends on PROFILING
|
||||
help
|
||||
Per UID based cpu time statistics exported to /proc/uid_cputime
|
||||
|
||||
source "drivers/misc/c2port/Kconfig"
|
||||
source "drivers/misc/eeprom/Kconfig"
|
||||
source "drivers/misc/cb710/Kconfig"
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ obj-$(CONFIG_ISL29020) += isl29020.o
|
|||
obj-$(CONFIG_SENSORS_TSL2550) += tsl2550.o
|
||||
obj-$(CONFIG_DS1682) += ds1682.o
|
||||
obj-$(CONFIG_TI_DAC7512) += ti_dac7512.o
|
||||
obj-$(CONFIG_UID_STAT) += uid_stat.o
|
||||
obj-$(CONFIG_C2PORT) += c2port/
|
||||
obj-$(CONFIG_HMC6352) += hmc6352.o
|
||||
obj-y += eeprom/
|
||||
|
|
@ -56,3 +57,4 @@ obj-$(CONFIG_GENWQE) += genwqe/
|
|||
obj-$(CONFIG_ECHO) += echo/
|
||||
obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
|
||||
obj-$(CONFIG_CXL_BASE) += cxl/
|
||||
obj-$(CONFIG_UID_CPUTIME) += uid_cputime.o
|
||||
|
|
|
|||
240
drivers/misc/uid_cputime.c
Normal file
240
drivers/misc/uid_cputime.c
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
/* drivers/misc/uid_cputime.c
|
||||
*
|
||||
* Copyright (C) 2014 - 2015 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/hashtable.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/profile.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#define UID_HASH_BITS 10
|
||||
DECLARE_HASHTABLE(hash_table, UID_HASH_BITS);
|
||||
|
||||
static DEFINE_MUTEX(uid_lock);
|
||||
static struct proc_dir_entry *parent;
|
||||
|
||||
struct uid_entry {
|
||||
uid_t uid;
|
||||
cputime_t utime;
|
||||
cputime_t stime;
|
||||
cputime_t active_utime;
|
||||
cputime_t active_stime;
|
||||
struct hlist_node hash;
|
||||
};
|
||||
|
||||
static struct uid_entry *find_uid_entry(uid_t uid)
|
||||
{
|
||||
struct uid_entry *uid_entry;
|
||||
hash_for_each_possible(hash_table, uid_entry, hash, uid) {
|
||||
if (uid_entry->uid == uid)
|
||||
return uid_entry;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct uid_entry *find_or_register_uid(uid_t uid)
|
||||
{
|
||||
struct uid_entry *uid_entry;
|
||||
|
||||
uid_entry = find_uid_entry(uid);
|
||||
if (uid_entry)
|
||||
return uid_entry;
|
||||
|
||||
uid_entry = kzalloc(sizeof(struct uid_entry), GFP_ATOMIC);
|
||||
if (!uid_entry)
|
||||
return NULL;
|
||||
|
||||
uid_entry->uid = uid;
|
||||
|
||||
hash_add(hash_table, &uid_entry->hash, uid);
|
||||
|
||||
return uid_entry;
|
||||
}
|
||||
|
||||
static int uid_stat_show(struct seq_file *m, void *v)
|
||||
{
|
||||
struct uid_entry *uid_entry;
|
||||
struct task_struct *task, *temp;
|
||||
cputime_t utime;
|
||||
cputime_t stime;
|
||||
unsigned long bkt;
|
||||
|
||||
mutex_lock(&uid_lock);
|
||||
|
||||
hash_for_each(hash_table, bkt, uid_entry, hash) {
|
||||
uid_entry->active_stime = 0;
|
||||
uid_entry->active_utime = 0;
|
||||
}
|
||||
|
||||
read_lock(&tasklist_lock);
|
||||
do_each_thread(temp, task) {
|
||||
uid_entry = find_or_register_uid(from_kuid_munged(
|
||||
current_user_ns(), task_uid(task)));
|
||||
if (!uid_entry) {
|
||||
read_unlock(&tasklist_lock);
|
||||
mutex_unlock(&uid_lock);
|
||||
pr_err("%s: failed to find the uid_entry for uid %d\n",
|
||||
__func__, from_kuid_munged(current_user_ns(),
|
||||
task_uid(task)));
|
||||
return -ENOMEM;
|
||||
}
|
||||
task_cputime_adjusted(task, &utime, &stime);
|
||||
uid_entry->active_utime += utime;
|
||||
uid_entry->active_stime += stime;
|
||||
} while_each_thread(temp, task);
|
||||
read_unlock(&tasklist_lock);
|
||||
|
||||
hash_for_each(hash_table, bkt, uid_entry, hash) {
|
||||
cputime_t total_utime = uid_entry->utime +
|
||||
uid_entry->active_utime;
|
||||
cputime_t total_stime = uid_entry->stime +
|
||||
uid_entry->active_stime;
|
||||
seq_printf(m, "%d: %llu %llu\n", uid_entry->uid,
|
||||
(unsigned long long)jiffies_to_msecs(
|
||||
cputime_to_jiffies(total_utime)) * USEC_PER_MSEC,
|
||||
(unsigned long long)jiffies_to_msecs(
|
||||
cputime_to_jiffies(total_stime)) * USEC_PER_MSEC);
|
||||
}
|
||||
|
||||
mutex_unlock(&uid_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uid_stat_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, uid_stat_show, PDE_DATA(inode));
|
||||
}
|
||||
|
||||
static const struct file_operations uid_stat_fops = {
|
||||
.open = uid_stat_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
static int uid_remove_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, NULL, NULL);
|
||||
}
|
||||
|
||||
static ssize_t uid_remove_write(struct file *file,
|
||||
const char __user *buffer, size_t count, loff_t *ppos)
|
||||
{
|
||||
struct uid_entry *uid_entry;
|
||||
struct hlist_node *tmp;
|
||||
char uids[128];
|
||||
char *start_uid, *end_uid = NULL;
|
||||
long int uid_start = 0, uid_end = 0;
|
||||
|
||||
if (count >= sizeof(uids))
|
||||
count = sizeof(uids) - 1;
|
||||
|
||||
if (copy_from_user(uids, buffer, count))
|
||||
return -EFAULT;
|
||||
|
||||
uids[count] = '\0';
|
||||
end_uid = uids;
|
||||
start_uid = strsep(&end_uid, "-");
|
||||
|
||||
if (!start_uid || !end_uid)
|
||||
return -EINVAL;
|
||||
|
||||
if (kstrtol(start_uid, 10, &uid_start) != 0 ||
|
||||
kstrtol(end_uid, 10, &uid_end) != 0) {
|
||||
return -EINVAL;
|
||||
}
|
||||
mutex_lock(&uid_lock);
|
||||
|
||||
for (; uid_start <= uid_end; uid_start++) {
|
||||
hash_for_each_possible_safe(hash_table, uid_entry, tmp,
|
||||
hash, (uid_t)uid_start) {
|
||||
if (uid_start == uid_entry->uid) {
|
||||
hash_del(&uid_entry->hash);
|
||||
kfree(uid_entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&uid_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct file_operations uid_remove_fops = {
|
||||
.open = uid_remove_open,
|
||||
.release = single_release,
|
||||
.write = uid_remove_write,
|
||||
};
|
||||
|
||||
static int process_notifier(struct notifier_block *self,
|
||||
unsigned long cmd, void *v)
|
||||
{
|
||||
struct task_struct *task = v;
|
||||
struct uid_entry *uid_entry;
|
||||
cputime_t utime, stime;
|
||||
uid_t uid;
|
||||
|
||||
if (!task)
|
||||
return NOTIFY_OK;
|
||||
|
||||
mutex_lock(&uid_lock);
|
||||
uid = from_kuid_munged(current_user_ns(), task_uid(task));
|
||||
uid_entry = find_or_register_uid(uid);
|
||||
if (!uid_entry) {
|
||||
pr_err("%s: failed to find uid %d\n", __func__, uid);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
task_cputime_adjusted(task, &utime, &stime);
|
||||
uid_entry->utime += utime;
|
||||
uid_entry->stime += stime;
|
||||
|
||||
exit:
|
||||
mutex_unlock(&uid_lock);
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static struct notifier_block process_notifier_block = {
|
||||
.notifier_call = process_notifier,
|
||||
};
|
||||
|
||||
static int __init proc_uid_cputime_init(void)
|
||||
{
|
||||
hash_init(hash_table);
|
||||
|
||||
parent = proc_mkdir("uid_cputime", NULL);
|
||||
if (!parent) {
|
||||
pr_err("%s: failed to create proc entry\n", __func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
proc_create_data("remove_uid_range", S_IWUGO, parent, &uid_remove_fops,
|
||||
NULL);
|
||||
|
||||
proc_create_data("show_uid_stat", S_IRUGO, parent, &uid_stat_fops,
|
||||
NULL);
|
||||
|
||||
profile_event_register(PROFILE_TASK_EXIT, &process_notifier_block);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
early_initcall(proc_uid_cputime_init);
|
||||
153
drivers/misc/uid_stat.c
Normal file
153
drivers/misc/uid_stat.c
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
/* drivers/misc/uid_stat.c
|
||||
*
|
||||
* Copyright (C) 2008 - 2009 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <asm/atomic.h>
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/stat.h>
|
||||
#include <linux/uid_stat.h>
|
||||
#include <net/activity_stats.h>
|
||||
|
||||
static DEFINE_SPINLOCK(uid_lock);
|
||||
static LIST_HEAD(uid_list);
|
||||
static struct proc_dir_entry *parent;
|
||||
|
||||
struct uid_stat {
|
||||
struct list_head link;
|
||||
uid_t uid;
|
||||
atomic_t tcp_rcv;
|
||||
atomic_t tcp_snd;
|
||||
};
|
||||
|
||||
static struct uid_stat *find_uid_stat(uid_t uid) {
|
||||
struct uid_stat *entry;
|
||||
|
||||
list_for_each_entry(entry, &uid_list, link) {
|
||||
if (entry->uid == uid) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int uid_stat_atomic_int_show(struct seq_file *m, void *v)
|
||||
{
|
||||
unsigned int bytes;
|
||||
atomic_t *counter = m->private;
|
||||
|
||||
bytes = (unsigned int) (atomic_read(counter) + INT_MIN);
|
||||
seq_printf(m, "%u\n", bytes);
|
||||
return seq_has_overflowed(m) ? -ENOSPC : 0;
|
||||
}
|
||||
|
||||
static int uid_stat_read_atomic_int_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, uid_stat_atomic_int_show, PDE_DATA(inode));
|
||||
}
|
||||
|
||||
static const struct file_operations uid_stat_read_atomic_int_fops = {
|
||||
.open = uid_stat_read_atomic_int_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = seq_release,
|
||||
};
|
||||
|
||||
/* Create a new entry for tracking the specified uid. */
|
||||
static struct uid_stat *create_stat(uid_t uid) {
|
||||
struct uid_stat *new_uid;
|
||||
/* Create the uid stat struct and append it to the list. */
|
||||
new_uid = kmalloc(sizeof(struct uid_stat), GFP_ATOMIC);
|
||||
if (!new_uid)
|
||||
return NULL;
|
||||
|
||||
new_uid->uid = uid;
|
||||
/* Counters start at INT_MIN, so we can track 4GB of network traffic. */
|
||||
atomic_set(&new_uid->tcp_rcv, INT_MIN);
|
||||
atomic_set(&new_uid->tcp_snd, INT_MIN);
|
||||
|
||||
list_add_tail(&new_uid->link, &uid_list);
|
||||
return new_uid;
|
||||
}
|
||||
|
||||
static void create_stat_proc(struct uid_stat *new_uid)
|
||||
{
|
||||
char uid_s[32];
|
||||
struct proc_dir_entry *entry;
|
||||
sprintf(uid_s, "%d", new_uid->uid);
|
||||
entry = proc_mkdir(uid_s, parent);
|
||||
|
||||
/* Keep reference to uid_stat so we know what uid to read stats from. */
|
||||
proc_create_data("tcp_snd", S_IRUGO, entry,
|
||||
&uid_stat_read_atomic_int_fops, &new_uid->tcp_snd);
|
||||
|
||||
proc_create_data("tcp_rcv", S_IRUGO, entry,
|
||||
&uid_stat_read_atomic_int_fops, &new_uid->tcp_rcv);
|
||||
}
|
||||
|
||||
static struct uid_stat *find_or_create_uid_stat(uid_t uid)
|
||||
{
|
||||
struct uid_stat *entry;
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&uid_lock, flags);
|
||||
entry = find_uid_stat(uid);
|
||||
if (entry) {
|
||||
spin_unlock_irqrestore(&uid_lock, flags);
|
||||
return entry;
|
||||
}
|
||||
entry = create_stat(uid);
|
||||
spin_unlock_irqrestore(&uid_lock, flags);
|
||||
if (entry)
|
||||
create_stat_proc(entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
int uid_stat_tcp_snd(uid_t uid, int size) {
|
||||
struct uid_stat *entry;
|
||||
activity_stats_update();
|
||||
entry = find_or_create_uid_stat(uid);
|
||||
if (!entry)
|
||||
return -1;
|
||||
atomic_add(size, &entry->tcp_snd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uid_stat_tcp_rcv(uid_t uid, int size) {
|
||||
struct uid_stat *entry;
|
||||
activity_stats_update();
|
||||
entry = find_or_create_uid_stat(uid);
|
||||
if (!entry)
|
||||
return -1;
|
||||
atomic_add(size, &entry->tcp_rcv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init uid_stat_init(void)
|
||||
{
|
||||
parent = proc_mkdir("uid_stat", NULL);
|
||||
if (!parent) {
|
||||
pr_err("uid_stat: failed to create proc entry\n");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
__initcall(uid_stat_init);
|
||||
|
|
@ -36,6 +36,9 @@
|
|||
#include <linux/compat.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include <trace/events/mmc.h>
|
||||
|
||||
#include <linux/mmc/ioctl.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/host.h>
|
||||
|
|
@ -47,13 +50,10 @@
|
|||
#include "queue.h"
|
||||
|
||||
MODULE_ALIAS("mmc:block");
|
||||
|
||||
#ifdef KERNEL
|
||||
#ifdef MODULE_PARAM_PREFIX
|
||||
#undef MODULE_PARAM_PREFIX
|
||||
#endif
|
||||
#define MODULE_PARAM_PREFIX "mmcblk."
|
||||
#endif
|
||||
|
||||
#define INAND_CMD38_ARG_EXT_CSD 113
|
||||
#define INAND_CMD38_ARG_ERASE 0x00
|
||||
|
|
@ -171,11 +171,7 @@ static struct mmc_blk_data *mmc_blk_get(struct gendisk *disk)
|
|||
|
||||
static inline int mmc_get_devidx(struct gendisk *disk)
|
||||
{
|
||||
int devmaj = MAJOR(disk_devt(disk));
|
||||
int devidx = MINOR(disk_devt(disk)) / perdev_minors;
|
||||
|
||||
if (!devmaj)
|
||||
devidx = disk->first_minor / perdev_minors;
|
||||
int devidx = disk->first_minor / perdev_minors;
|
||||
return devidx;
|
||||
}
|
||||
|
||||
|
|
@ -450,9 +446,11 @@ static int ioctl_do_sanitize(struct mmc_card *card)
|
|||
pr_debug("%s: %s - SANITIZE IN PROGRESS...\n",
|
||||
mmc_hostname(card->host), __func__);
|
||||
|
||||
trace_mmc_blk_erase_start(EXT_CSD_SANITIZE_START, 0, 0);
|
||||
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
|
||||
EXT_CSD_SANITIZE_START, 1,
|
||||
MMC_SANITIZE_REQ_TIMEOUT);
|
||||
trace_mmc_blk_erase_end(EXT_CSD_SANITIZE_START, 0, 0);
|
||||
|
||||
if (err)
|
||||
pr_err("%s: %s - EXT_CSD_SANITIZE_START failed. err=%d\n",
|
||||
|
|
@ -944,18 +942,22 @@ static int mmc_blk_cmd_error(struct request *req, const char *name, int error,
|
|||
req->rq_disk->disk_name, "timed out", name, status);
|
||||
|
||||
/* If the status cmd initially failed, retry the r/w cmd */
|
||||
if (!status_valid)
|
||||
if (!status_valid) {
|
||||
pr_err("%s: status not valid, retrying timeout\n", req->rq_disk->disk_name);
|
||||
return ERR_RETRY;
|
||||
|
||||
}
|
||||
/*
|
||||
* If it was a r/w cmd crc error, or illegal command
|
||||
* (eg, issued in wrong state) then retry - we should
|
||||
* have corrected the state problem above.
|
||||
*/
|
||||
if (status & (R1_COM_CRC_ERROR | R1_ILLEGAL_COMMAND))
|
||||
if (status & (R1_COM_CRC_ERROR | R1_ILLEGAL_COMMAND)) {
|
||||
pr_err("%s: command error, retrying timeout\n", req->rq_disk->disk_name);
|
||||
return ERR_RETRY;
|
||||
}
|
||||
|
||||
/* Otherwise abort the command */
|
||||
pr_err("%s: not retrying timeout\n", req->rq_disk->disk_name);
|
||||
return ERR_ABORT;
|
||||
|
||||
default:
|
||||
|
|
@ -2244,6 +2246,7 @@ static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card,
|
|||
md->disk->queue = md->queue.queue;
|
||||
md->disk->driverfs_dev = parent;
|
||||
set_disk_ro(md->disk, md->read_only || default_ro);
|
||||
md->disk->flags = GENHD_FL_EXT_DEVT;
|
||||
if (area_type & (MMC_BLK_DATA_AREA_RPMB | MMC_BLK_DATA_AREA_BOOT))
|
||||
md->disk->flags |= GENHD_FL_NO_PART_SCAN;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,18 @@
|
|||
#
|
||||
# MMC core configuration
|
||||
#
|
||||
|
||||
config MMC_EMBEDDED_SDIO
|
||||
boolean "MMC embedded SDIO device support (EXPERIMENTAL)"
|
||||
help
|
||||
If you say Y here, support will be added for embedded SDIO
|
||||
devices which do not contain the necessary enumeration
|
||||
support in hardware to be properly detected.
|
||||
|
||||
config MMC_PARANOID_SD_INIT
|
||||
bool "Enable paranoid SD card initialization (EXPERIMENTAL)"
|
||||
help
|
||||
If you say Y here, the MMC layer will be extra paranoid
|
||||
about re-trying SD init requests. This can be a useful
|
||||
work-around for buggy controllers and hardware. Enable
|
||||
if you are experiencing issues with SD detection.
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@
|
|||
#include <linux/random.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/wakelock.h>
|
||||
|
||||
#include <trace/events/mmc.h>
|
||||
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/host.h>
|
||||
|
|
@ -56,6 +59,7 @@
|
|||
#define MMC_BKOPS_MAX_TIMEOUT (4 * 60 * 1000) /* max time to wait in ms */
|
||||
|
||||
static struct workqueue_struct *workqueue;
|
||||
static struct wake_lock mmc_delayed_work_wake_lock;
|
||||
static const unsigned freqs[] = { 400000, 300000, 200000, 100000 };
|
||||
|
||||
/*
|
||||
|
|
@ -72,6 +76,7 @@ module_param(use_spi_crc, bool, 0);
|
|||
static int mmc_schedule_delayed_work(struct delayed_work *work,
|
||||
unsigned long delay)
|
||||
{
|
||||
wake_lock(&mmc_delayed_work_wake_lock);
|
||||
return queue_delayed_work(workqueue, work, delay);
|
||||
}
|
||||
|
||||
|
|
@ -175,6 +180,7 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq)
|
|||
pr_debug("%s: %d bytes transferred: %d\n",
|
||||
mmc_hostname(host),
|
||||
mrq->data->bytes_xfered, mrq->data->error);
|
||||
trace_mmc_blk_rw_end(cmd->opcode, cmd->arg, mrq->data);
|
||||
}
|
||||
|
||||
if (mrq->stop) {
|
||||
|
|
@ -617,8 +623,12 @@ struct mmc_async_req *mmc_start_req(struct mmc_host *host,
|
|||
}
|
||||
}
|
||||
|
||||
if (!err && areq)
|
||||
if (!err && areq) {
|
||||
trace_mmc_blk_rw_start(areq->mrq->cmd->opcode,
|
||||
areq->mrq->cmd->arg,
|
||||
areq->mrq->data);
|
||||
start_err = __mmc_start_data_req(host, areq->mrq);
|
||||
}
|
||||
|
||||
if (host->areq)
|
||||
mmc_post_req(host, host->areq->mrq, 0);
|
||||
|
|
@ -2055,8 +2065,13 @@ static int mmc_do_erase(struct mmc_card *card, unsigned int from,
|
|||
struct mmc_command cmd = {0};
|
||||
unsigned int qty = 0;
|
||||
unsigned long timeout;
|
||||
unsigned int fr, nr;
|
||||
int err;
|
||||
|
||||
fr = from;
|
||||
nr = to - from + 1;
|
||||
trace_mmc_blk_erase_start(arg, fr, nr);
|
||||
|
||||
mmc_retune_hold(card->host);
|
||||
|
||||
/*
|
||||
|
|
@ -2163,6 +2178,7 @@ static int mmc_do_erase(struct mmc_card *card, unsigned int from,
|
|||
(R1_CURRENT_STATE(cmd.resp[0]) == R1_STATE_PRG));
|
||||
out:
|
||||
mmc_retune_release(card->host);
|
||||
trace_mmc_blk_erase_end(arg, fr, nr);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
@ -2566,6 +2582,7 @@ void mmc_rescan(struct work_struct *work)
|
|||
struct mmc_host *host =
|
||||
container_of(work, struct mmc_host, detect.work);
|
||||
int i;
|
||||
bool extend_wakelock = false;
|
||||
|
||||
if (host->trigger_card_event && host->ops->card_event) {
|
||||
host->ops->card_event(host);
|
||||
|
|
@ -2592,6 +2609,12 @@ void mmc_rescan(struct work_struct *work)
|
|||
|
||||
host->detect_change = 0;
|
||||
|
||||
/* If the card was removed the bus will be marked
|
||||
* as dead - extend the wakelock so userspace
|
||||
* can respond */
|
||||
if (host->bus_dead)
|
||||
extend_wakelock = 1;
|
||||
|
||||
/*
|
||||
* Let mmc_bus_put() free the bus/bus_ops if we've found that
|
||||
* the card is no longer present.
|
||||
|
|
@ -2621,14 +2644,20 @@ void mmc_rescan(struct work_struct *work)
|
|||
|
||||
mmc_claim_host(host);
|
||||
for (i = 0; i < ARRAY_SIZE(freqs); i++) {
|
||||
if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min)))
|
||||
if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min))) {
|
||||
extend_wakelock = true;
|
||||
break;
|
||||
}
|
||||
if (freqs[i] <= host->f_min)
|
||||
break;
|
||||
}
|
||||
mmc_release_host(host);
|
||||
|
||||
out:
|
||||
if (extend_wakelock)
|
||||
wake_lock_timeout(&mmc_delayed_work_wake_lock, HZ / 2);
|
||||
else
|
||||
wake_unlock(&mmc_delayed_work_wake_lock);
|
||||
if (host->caps & MMC_CAP_NEEDS_POLL)
|
||||
mmc_schedule_delayed_work(&host->detect, HZ);
|
||||
}
|
||||
|
|
@ -2832,6 +2861,22 @@ void mmc_init_context_info(struct mmc_host *host)
|
|||
init_waitqueue_head(&host->context_info.wait);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MMC_EMBEDDED_SDIO
|
||||
void mmc_set_embedded_sdio_data(struct mmc_host *host,
|
||||
struct sdio_cis *cis,
|
||||
struct sdio_cccr *cccr,
|
||||
struct sdio_embedded_func *funcs,
|
||||
int num_funcs)
|
||||
{
|
||||
host->embedded_sdio_data.cis = cis;
|
||||
host->embedded_sdio_data.cccr = cccr;
|
||||
host->embedded_sdio_data.funcs = funcs;
|
||||
host->embedded_sdio_data.num_funcs = num_funcs;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_set_embedded_sdio_data);
|
||||
#endif
|
||||
|
||||
static int __init mmc_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
|
@ -2840,6 +2885,9 @@ static int __init mmc_init(void)
|
|||
if (!workqueue)
|
||||
return -ENOMEM;
|
||||
|
||||
wake_lock_init(&mmc_delayed_work_wake_lock, WAKE_LOCK_SUSPEND,
|
||||
"mmc_delayed_work");
|
||||
|
||||
ret = mmc_register_bus();
|
||||
if (ret)
|
||||
goto destroy_workqueue;
|
||||
|
|
@ -2860,6 +2908,7 @@ unregister_bus:
|
|||
mmc_unregister_bus();
|
||||
destroy_workqueue:
|
||||
destroy_workqueue(workqueue);
|
||||
wake_lock_destroy(&mmc_delayed_work_wake_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -2870,6 +2919,7 @@ static void __exit mmc_exit(void)
|
|||
mmc_unregister_host_class();
|
||||
mmc_unregister_bus();
|
||||
destroy_workqueue(workqueue);
|
||||
wake_lock_destroy(&mmc_delayed_work_wake_lock);
|
||||
}
|
||||
|
||||
subsys_initcall(mmc_init);
|
||||
|
|
|
|||
|
|
@ -395,7 +395,8 @@ int mmc_add_host(struct mmc_host *host)
|
|||
#endif
|
||||
|
||||
mmc_start_host(host);
|
||||
register_pm_notifier(&host->pm_notify);
|
||||
if (!(host->pm_flags & MMC_PM_IGNORE_PM_NOTIFY))
|
||||
register_pm_notifier(&host->pm_notify);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -412,7 +413,9 @@ EXPORT_SYMBOL(mmc_add_host);
|
|||
*/
|
||||
void mmc_remove_host(struct mmc_host *host)
|
||||
{
|
||||
unregister_pm_notifier(&host->pm_notify);
|
||||
if (!(host->pm_flags & MMC_PM_IGNORE_PM_NOTIFY))
|
||||
unregister_pm_notifier(&host->pm_notify);
|
||||
|
||||
mmc_stop_host(host);
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
|
|
|
|||
|
|
@ -809,6 +809,9 @@ int mmc_sd_setup_card(struct mmc_host *host, struct mmc_card *card,
|
|||
bool reinit)
|
||||
{
|
||||
int err;
|
||||
#ifdef CONFIG_MMC_PARANOID_SD_INIT
|
||||
int retries;
|
||||
#endif
|
||||
|
||||
if (!reinit) {
|
||||
/*
|
||||
|
|
@ -835,7 +838,26 @@ int mmc_sd_setup_card(struct mmc_host *host, struct mmc_card *card,
|
|||
/*
|
||||
* Fetch switch information from card.
|
||||
*/
|
||||
#ifdef CONFIG_MMC_PARANOID_SD_INIT
|
||||
for (retries = 1; retries <= 3; retries++) {
|
||||
err = mmc_read_switch(card);
|
||||
if (!err) {
|
||||
if (retries > 1) {
|
||||
printk(KERN_WARNING
|
||||
"%s: recovered\n",
|
||||
mmc_hostname(host));
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
printk(KERN_WARNING
|
||||
"%s: read switch failed (attempt %d)\n",
|
||||
mmc_hostname(host), retries);
|
||||
}
|
||||
}
|
||||
#else
|
||||
err = mmc_read_switch(card);
|
||||
#endif
|
||||
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
|
@ -1033,7 +1055,10 @@ static int mmc_sd_alive(struct mmc_host *host)
|
|||
*/
|
||||
static void mmc_sd_detect(struct mmc_host *host)
|
||||
{
|
||||
int err;
|
||||
int err = 0;
|
||||
#ifdef CONFIG_MMC_PARANOID_SD_INIT
|
||||
int retries = 5;
|
||||
#endif
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!host->card);
|
||||
|
|
@ -1043,7 +1068,23 @@ static void mmc_sd_detect(struct mmc_host *host)
|
|||
/*
|
||||
* Just check if our card has been removed.
|
||||
*/
|
||||
#ifdef CONFIG_MMC_PARANOID_SD_INIT
|
||||
while(retries) {
|
||||
err = mmc_send_status(host->card, NULL);
|
||||
if (err) {
|
||||
retries--;
|
||||
udelay(5);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!retries) {
|
||||
printk(KERN_ERR "%s(%s): Unable to re-detect card (%d)\n",
|
||||
__func__, mmc_hostname(host), err);
|
||||
}
|
||||
#else
|
||||
err = _mmc_detect_card_removed(host);
|
||||
#endif
|
||||
|
||||
mmc_put_card(host->card);
|
||||
|
||||
|
|
@ -1105,6 +1146,9 @@ static int mmc_sd_suspend(struct mmc_host *host)
|
|||
static int _mmc_sd_resume(struct mmc_host *host)
|
||||
{
|
||||
int err = 0;
|
||||
#ifdef CONFIG_MMC_PARANOID_SD_INIT
|
||||
int retries;
|
||||
#endif
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!host->card);
|
||||
|
|
@ -1115,7 +1159,23 @@ static int _mmc_sd_resume(struct mmc_host *host)
|
|||
goto out;
|
||||
|
||||
mmc_power_up(host, host->card->ocr);
|
||||
#ifdef CONFIG_MMC_PARANOID_SD_INIT
|
||||
retries = 5;
|
||||
while (retries) {
|
||||
err = mmc_sd_init_card(host, host->card->ocr, host->card);
|
||||
|
||||
if (err) {
|
||||
printk(KERN_ERR "%s: Re-init card rc = %d (retries = %d)\n",
|
||||
mmc_hostname(host), err, retries);
|
||||
mdelay(5);
|
||||
retries--;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
#else
|
||||
err = mmc_sd_init_card(host, host->card->ocr, host->card);
|
||||
#endif
|
||||
mmc_card_clr_suspended(host->card);
|
||||
|
||||
out:
|
||||
|
|
@ -1201,6 +1261,9 @@ int mmc_attach_sd(struct mmc_host *host)
|
|||
{
|
||||
int err;
|
||||
u32 ocr, rocr;
|
||||
#ifdef CONFIG_MMC_PARANOID_SD_INIT
|
||||
int retries;
|
||||
#endif
|
||||
|
||||
BUG_ON(!host);
|
||||
WARN_ON(!host->claimed);
|
||||
|
|
@ -1237,9 +1300,27 @@ int mmc_attach_sd(struct mmc_host *host)
|
|||
/*
|
||||
* Detect and init the card.
|
||||
*/
|
||||
#ifdef CONFIG_MMC_PARANOID_SD_INIT
|
||||
retries = 5;
|
||||
while (retries) {
|
||||
err = mmc_sd_init_card(host, rocr, NULL);
|
||||
if (err) {
|
||||
retries--;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!retries) {
|
||||
printk(KERN_ERR "%s: mmc_sd_init_card() failure (err = %d)\n",
|
||||
mmc_hostname(host), err);
|
||||
goto err;
|
||||
}
|
||||
#else
|
||||
err = mmc_sd_init_card(host, rocr, NULL);
|
||||
if (err)
|
||||
goto err;
|
||||
#endif
|
||||
|
||||
mmc_release_host(host);
|
||||
err = mmc_add_card(host->card);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
|
|
@ -28,6 +29,10 @@
|
|||
#include "sdio_ops.h"
|
||||
#include "sdio_cis.h"
|
||||
|
||||
#ifdef CONFIG_MMC_EMBEDDED_SDIO
|
||||
#include <linux/mmc/sdio_ids.h>
|
||||
#endif
|
||||
|
||||
static int sdio_read_fbr(struct sdio_func *func)
|
||||
{
|
||||
int ret;
|
||||
|
|
@ -699,19 +704,35 @@ try_again:
|
|||
goto finish;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the common registers.
|
||||
*/
|
||||
err = sdio_read_cccr(card, ocr);
|
||||
if (err)
|
||||
goto remove;
|
||||
#ifdef CONFIG_MMC_EMBEDDED_SDIO
|
||||
if (host->embedded_sdio_data.cccr)
|
||||
memcpy(&card->cccr, host->embedded_sdio_data.cccr, sizeof(struct sdio_cccr));
|
||||
else {
|
||||
#endif
|
||||
/*
|
||||
* Read the common registers.
|
||||
*/
|
||||
err = sdio_read_cccr(card, ocr);
|
||||
if (err)
|
||||
goto remove;
|
||||
#ifdef CONFIG_MMC_EMBEDDED_SDIO
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Read the common CIS tuples.
|
||||
*/
|
||||
err = sdio_read_common_cis(card);
|
||||
if (err)
|
||||
goto remove;
|
||||
#ifdef CONFIG_MMC_EMBEDDED_SDIO
|
||||
if (host->embedded_sdio_data.cis)
|
||||
memcpy(&card->cis, host->embedded_sdio_data.cis, sizeof(struct sdio_cis));
|
||||
else {
|
||||
#endif
|
||||
/*
|
||||
* Read the common CIS tuples.
|
||||
*/
|
||||
err = sdio_read_common_cis(card);
|
||||
if (err)
|
||||
goto remove;
|
||||
#ifdef CONFIG_MMC_EMBEDDED_SDIO
|
||||
}
|
||||
#endif
|
||||
|
||||
if (oldcard) {
|
||||
int same = (card->cis.vendor == oldcard->cis.vendor &&
|
||||
|
|
@ -1120,14 +1141,36 @@ int mmc_attach_sdio(struct mmc_host *host)
|
|||
funcs = (ocr & 0x70000000) >> 28;
|
||||
card->sdio_funcs = 0;
|
||||
|
||||
#ifdef CONFIG_MMC_EMBEDDED_SDIO
|
||||
if (host->embedded_sdio_data.funcs)
|
||||
card->sdio_funcs = funcs = host->embedded_sdio_data.num_funcs;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Initialize (but don't add) all present functions.
|
||||
*/
|
||||
for (i = 0; i < funcs; i++, card->sdio_funcs++) {
|
||||
err = sdio_init_func(host->card, i + 1);
|
||||
if (err)
|
||||
goto remove;
|
||||
#ifdef CONFIG_MMC_EMBEDDED_SDIO
|
||||
if (host->embedded_sdio_data.funcs) {
|
||||
struct sdio_func *tmp;
|
||||
|
||||
tmp = sdio_alloc_func(host->card);
|
||||
if (IS_ERR(tmp))
|
||||
goto remove;
|
||||
tmp->num = (i + 1);
|
||||
card->sdio_func[i] = tmp;
|
||||
tmp->class = host->embedded_sdio_data.funcs[i].f_class;
|
||||
tmp->max_blksize = host->embedded_sdio_data.funcs[i].f_maxblksize;
|
||||
tmp->vendor = card->cis.vendor;
|
||||
tmp->device = card->cis.device;
|
||||
} else {
|
||||
#endif
|
||||
err = sdio_init_func(host->card, i + 1);
|
||||
if (err)
|
||||
goto remove;
|
||||
#ifdef CONFIG_MMC_EMBEDDED_SDIO
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
* Enable Runtime PM for this func (if supported)
|
||||
*/
|
||||
|
|
@ -1175,3 +1218,40 @@ err:
|
|||
return err;
|
||||
}
|
||||
|
||||
int sdio_reset_comm(struct mmc_card *card)
|
||||
{
|
||||
struct mmc_host *host = card->host;
|
||||
u32 ocr;
|
||||
u32 rocr;
|
||||
int err;
|
||||
|
||||
printk("%s():\n", __func__);
|
||||
mmc_claim_host(host);
|
||||
|
||||
mmc_go_idle(host);
|
||||
|
||||
mmc_set_clock(host, host->f_min);
|
||||
|
||||
err = mmc_send_io_op_cond(host, 0, &ocr);
|
||||
if (err)
|
||||
goto err;
|
||||
|
||||
rocr = mmc_select_voltage(host, ocr);
|
||||
if (!rocr) {
|
||||
err = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
err = mmc_sdio_init_card(host, rocr, card, 0);
|
||||
if (err)
|
||||
goto err;
|
||||
|
||||
mmc_release_host(host);
|
||||
return 0;
|
||||
err:
|
||||
printk("%s: Error resetting SDIO communications (%d)\n",
|
||||
mmc_hostname(host), err);
|
||||
mmc_release_host(host);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL(sdio_reset_comm);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@
|
|||
#include "sdio_cis.h"
|
||||
#include "sdio_bus.h"
|
||||
|
||||
#ifdef CONFIG_MMC_EMBEDDED_SDIO
|
||||
#include <linux/mmc/host.h>
|
||||
#endif
|
||||
|
||||
#define to_sdio_driver(d) container_of(d, struct sdio_driver, drv)
|
||||
|
||||
/* show configuration fields */
|
||||
|
|
@ -263,7 +267,14 @@ static void sdio_release_func(struct device *dev)
|
|||
{
|
||||
struct sdio_func *func = dev_to_sdio_func(dev);
|
||||
|
||||
sdio_free_func_cis(func);
|
||||
#ifdef CONFIG_MMC_EMBEDDED_SDIO
|
||||
/*
|
||||
* If this device is embedded then we never allocated
|
||||
* cis tables for this func
|
||||
*/
|
||||
if (!func->card->host->embedded_sdio_data.funcs)
|
||||
#endif
|
||||
sdio_free_func_cis(func);
|
||||
|
||||
kfree(func->info);
|
||||
|
||||
|
|
|
|||
|
|
@ -383,6 +383,39 @@ u8 sdio_readb(struct sdio_func *func, unsigned int addr, int *err_ret)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_readb);
|
||||
|
||||
/**
|
||||
* sdio_readb_ext - read a single byte from a SDIO function
|
||||
* @func: SDIO function to access
|
||||
* @addr: address to read
|
||||
* @err_ret: optional status value from transfer
|
||||
* @in: value to add to argument
|
||||
*
|
||||
* Reads a single byte from the address space of a given SDIO
|
||||
* function. If there is a problem reading the address, 0xff
|
||||
* is returned and @err_ret will contain the error code.
|
||||
*/
|
||||
unsigned char sdio_readb_ext(struct sdio_func *func, unsigned int addr,
|
||||
int *err_ret, unsigned in)
|
||||
{
|
||||
int ret;
|
||||
unsigned char val;
|
||||
|
||||
BUG_ON(!func);
|
||||
|
||||
if (err_ret)
|
||||
*err_ret = 0;
|
||||
|
||||
ret = mmc_io_rw_direct(func->card, 0, func->num, addr, (u8)in, &val);
|
||||
if (ret) {
|
||||
if (err_ret)
|
||||
*err_ret = ret;
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdio_readb_ext);
|
||||
|
||||
/**
|
||||
* sdio_writeb - write a single byte to a SDIO function
|
||||
* @func: SDIO function to access
|
||||
|
|
|
|||
|
|
@ -1,3 +1,10 @@
|
|||
config MTD_NAND_IDS
|
||||
tristate "Include chip ids for known NAND devices."
|
||||
depends on MTD
|
||||
help
|
||||
Useful for NAND drivers that do not use the NAND subsystem but
|
||||
still like to take advantage of the known chip information.
|
||||
|
||||
config MTD_NAND_ECC
|
||||
tristate
|
||||
|
||||
|
|
@ -108,9 +115,6 @@ config MTD_NAND_OMAP_BCH
|
|||
config MTD_NAND_OMAP_BCH_BUILD
|
||||
def_tristate MTD_NAND_OMAP2 && MTD_NAND_OMAP_BCH
|
||||
|
||||
config MTD_NAND_IDS
|
||||
tristate
|
||||
|
||||
config MTD_NAND_RICOH
|
||||
tristate "Ricoh xD card reader"
|
||||
default n
|
||||
|
|
|
|||
|
|
@ -149,6 +149,23 @@ config PPPOL2TP
|
|||
tunnels. L2TP is replacing PPTP for VPN uses.
|
||||
if TTY
|
||||
|
||||
config PPPOLAC
|
||||
bool "PPP on L2TP Access Concentrator"
|
||||
depends on PPP && INET
|
||||
help
|
||||
L2TP (RFC 2661) is a tunneling protocol widely used in virtual private
|
||||
networks. This driver handles L2TP data packets between a UDP socket
|
||||
and a PPP channel, but only permits one session per socket. Thus it is
|
||||
fairly simple and suited for clients.
|
||||
|
||||
config PPPOPNS
|
||||
bool "PPP on PPTP Network Server"
|
||||
depends on PPP && INET
|
||||
help
|
||||
PPTP (RFC 2637) is a tunneling protocol widely used in virtual private
|
||||
networks. This driver handles PPTP data packets between a RAW socket
|
||||
and a PPP channel. It is fairly simple and easy to use.
|
||||
|
||||
config PPP_ASYNC
|
||||
tristate "PPP support for async serial ports"
|
||||
depends on PPP
|
||||
|
|
|
|||
|
|
@ -11,3 +11,5 @@ obj-$(CONFIG_PPP_SYNC_TTY) += ppp_synctty.o
|
|||
obj-$(CONFIG_PPPOE) += pppox.o pppoe.o
|
||||
obj-$(CONFIG_PPPOL2TP) += pppox.o
|
||||
obj-$(CONFIG_PPTP) += pppox.o pptp.o
|
||||
obj-$(CONFIG_PPPOLAC) += pppox.o pppolac.o
|
||||
obj-$(CONFIG_PPPOPNS) += pppox.o pppopns.o
|
||||
|
|
|
|||
449
drivers/net/ppp/pppolac.c
Normal file
449
drivers/net/ppp/pppolac.c
Normal file
|
|
@ -0,0 +1,449 @@
|
|||
/* drivers/net/pppolac.c
|
||||
*
|
||||
* Driver for PPP on L2TP Access Concentrator / PPPoLAC Socket (RFC 2661)
|
||||
*
|
||||
* Copyright (C) 2009 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* This driver handles L2TP data packets between a UDP socket and a PPP channel.
|
||||
* The socket must keep connected, and only one session per socket is permitted.
|
||||
* Sequencing of outgoing packets is controlled by LNS. Incoming packets with
|
||||
* sequences are reordered within a sliding window of one second. Currently
|
||||
* reordering only happens when a packet is received. It is done for simplicity
|
||||
* since no additional locks or threads are required. This driver only works on
|
||||
* IPv4 due to the lack of UDP encapsulation support in IPv6. */
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/net.h>
|
||||
#include <linux/udp.h>
|
||||
#include <linux/ppp_defs.h>
|
||||
#include <linux/if_ppp.h>
|
||||
#include <linux/if_pppox.h>
|
||||
#include <linux/ppp_channel.h>
|
||||
#include <net/tcp_states.h>
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
#define L2TP_CONTROL_BIT 0x80
|
||||
#define L2TP_LENGTH_BIT 0x40
|
||||
#define L2TP_SEQUENCE_BIT 0x08
|
||||
#define L2TP_OFFSET_BIT 0x02
|
||||
#define L2TP_VERSION 0x02
|
||||
#define L2TP_VERSION_MASK 0x0F
|
||||
|
||||
#define PPP_ADDR 0xFF
|
||||
#define PPP_CTRL 0x03
|
||||
|
||||
union unaligned {
|
||||
__u32 u32;
|
||||
} __attribute__((packed));
|
||||
|
||||
static inline union unaligned *unaligned(void *ptr)
|
||||
{
|
||||
return (union unaligned *)ptr;
|
||||
}
|
||||
|
||||
struct meta {
|
||||
__u32 sequence;
|
||||
__u32 timestamp;
|
||||
};
|
||||
|
||||
static inline struct meta *skb_meta(struct sk_buff *skb)
|
||||
{
|
||||
return (struct meta *)skb->cb;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
static int pppolac_recv_core(struct sock *sk_udp, struct sk_buff *skb)
|
||||
{
|
||||
struct sock *sk = (struct sock *)sk_udp->sk_user_data;
|
||||
struct pppolac_opt *opt = &pppox_sk(sk)->proto.lac;
|
||||
struct meta *meta = skb_meta(skb);
|
||||
__u32 now = jiffies;
|
||||
__u8 bits;
|
||||
__u8 *ptr;
|
||||
|
||||
/* Drop the packet if L2TP header is missing. */
|
||||
if (skb->len < sizeof(struct udphdr) + 6)
|
||||
goto drop;
|
||||
|
||||
/* Put it back if it is a control packet. */
|
||||
if (skb->data[sizeof(struct udphdr)] & L2TP_CONTROL_BIT)
|
||||
return opt->backlog_rcv(sk_udp, skb);
|
||||
|
||||
/* Skip UDP header. */
|
||||
skb_pull(skb, sizeof(struct udphdr));
|
||||
|
||||
/* Check the version. */
|
||||
if ((skb->data[1] & L2TP_VERSION_MASK) != L2TP_VERSION)
|
||||
goto drop;
|
||||
bits = skb->data[0];
|
||||
ptr = &skb->data[2];
|
||||
|
||||
/* Check the length if it is present. */
|
||||
if (bits & L2TP_LENGTH_BIT) {
|
||||
if ((ptr[0] << 8 | ptr[1]) != skb->len)
|
||||
goto drop;
|
||||
ptr += 2;
|
||||
}
|
||||
|
||||
/* Skip all fields including optional ones. */
|
||||
if (!skb_pull(skb, 6 + (bits & L2TP_SEQUENCE_BIT ? 4 : 0) +
|
||||
(bits & L2TP_LENGTH_BIT ? 2 : 0) +
|
||||
(bits & L2TP_OFFSET_BIT ? 2 : 0)))
|
||||
goto drop;
|
||||
|
||||
/* Skip the offset padding if it is present. */
|
||||
if (bits & L2TP_OFFSET_BIT &&
|
||||
!skb_pull(skb, skb->data[-2] << 8 | skb->data[-1]))
|
||||
goto drop;
|
||||
|
||||
/* Check the tunnel and the session. */
|
||||
if (unaligned(ptr)->u32 != opt->local)
|
||||
goto drop;
|
||||
|
||||
/* Check the sequence if it is present. */
|
||||
if (bits & L2TP_SEQUENCE_BIT) {
|
||||
meta->sequence = ptr[4] << 8 | ptr[5];
|
||||
if ((__s16)(meta->sequence - opt->recv_sequence) < 0)
|
||||
goto drop;
|
||||
}
|
||||
|
||||
/* Skip PPP address and control if they are present. */
|
||||
if (skb->len >= 2 && skb->data[0] == PPP_ADDR &&
|
||||
skb->data[1] == PPP_CTRL)
|
||||
skb_pull(skb, 2);
|
||||
|
||||
/* Fix PPP protocol if it is compressed. */
|
||||
if (skb->len >= 1 && skb->data[0] & 1)
|
||||
skb_push(skb, 1)[0] = 0;
|
||||
|
||||
/* Drop the packet if PPP protocol is missing. */
|
||||
if (skb->len < 2)
|
||||
goto drop;
|
||||
|
||||
/* Perform reordering if sequencing is enabled. */
|
||||
atomic_set(&opt->sequencing, bits & L2TP_SEQUENCE_BIT);
|
||||
if (bits & L2TP_SEQUENCE_BIT) {
|
||||
struct sk_buff *skb1;
|
||||
|
||||
/* Insert the packet into receive queue in order. */
|
||||
skb_set_owner_r(skb, sk);
|
||||
skb_queue_walk(&sk->sk_receive_queue, skb1) {
|
||||
struct meta *meta1 = skb_meta(skb1);
|
||||
__s16 order = meta->sequence - meta1->sequence;
|
||||
if (order == 0)
|
||||
goto drop;
|
||||
if (order < 0) {
|
||||
meta->timestamp = meta1->timestamp;
|
||||
skb_insert(skb1, skb, &sk->sk_receive_queue);
|
||||
skb = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (skb) {
|
||||
meta->timestamp = now;
|
||||
skb_queue_tail(&sk->sk_receive_queue, skb);
|
||||
}
|
||||
|
||||
/* Remove packets from receive queue as long as
|
||||
* 1. the receive buffer is full,
|
||||
* 2. they are queued longer than one second, or
|
||||
* 3. there are no missing packets before them. */
|
||||
skb_queue_walk_safe(&sk->sk_receive_queue, skb, skb1) {
|
||||
meta = skb_meta(skb);
|
||||
if (atomic_read(&sk->sk_rmem_alloc) < sk->sk_rcvbuf &&
|
||||
now - meta->timestamp < HZ &&
|
||||
meta->sequence != opt->recv_sequence)
|
||||
break;
|
||||
skb_unlink(skb, &sk->sk_receive_queue);
|
||||
opt->recv_sequence = (__u16)(meta->sequence + 1);
|
||||
skb_orphan(skb);
|
||||
ppp_input(&pppox_sk(sk)->chan, skb);
|
||||
}
|
||||
return NET_RX_SUCCESS;
|
||||
}
|
||||
|
||||
/* Flush receive queue if sequencing is disabled. */
|
||||
skb_queue_purge(&sk->sk_receive_queue);
|
||||
skb_orphan(skb);
|
||||
ppp_input(&pppox_sk(sk)->chan, skb);
|
||||
return NET_RX_SUCCESS;
|
||||
drop:
|
||||
kfree_skb(skb);
|
||||
return NET_RX_DROP;
|
||||
}
|
||||
|
||||
static int pppolac_recv(struct sock *sk_udp, struct sk_buff *skb)
|
||||
{
|
||||
sock_hold(sk_udp);
|
||||
sk_receive_skb(sk_udp, skb, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct sk_buff_head delivery_queue;
|
||||
|
||||
static void pppolac_xmit_core(struct work_struct *delivery_work)
|
||||
{
|
||||
mm_segment_t old_fs = get_fs();
|
||||
struct sk_buff *skb;
|
||||
|
||||
set_fs(KERNEL_DS);
|
||||
while ((skb = skb_dequeue(&delivery_queue))) {
|
||||
struct sock *sk_udp = skb->sk;
|
||||
struct kvec iov = {.iov_base = skb->data, .iov_len = skb->len};
|
||||
struct msghdr msg = {
|
||||
.msg_iov = (struct iovec *)&iov,
|
||||
.msg_iovlen = 1,
|
||||
.msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT,
|
||||
};
|
||||
sk_udp->sk_prot->sendmsg(sk_udp, &msg, skb->len);
|
||||
kfree_skb(skb);
|
||||
}
|
||||
set_fs(old_fs);
|
||||
}
|
||||
|
||||
static DECLARE_WORK(delivery_work, pppolac_xmit_core);
|
||||
|
||||
static int pppolac_xmit(struct ppp_channel *chan, struct sk_buff *skb)
|
||||
{
|
||||
struct sock *sk_udp = (struct sock *)chan->private;
|
||||
struct pppolac_opt *opt = &pppox_sk(sk_udp->sk_user_data)->proto.lac;
|
||||
|
||||
/* Install PPP address and control. */
|
||||
skb_push(skb, 2);
|
||||
skb->data[0] = PPP_ADDR;
|
||||
skb->data[1] = PPP_CTRL;
|
||||
|
||||
/* Install L2TP header. */
|
||||
if (atomic_read(&opt->sequencing)) {
|
||||
skb_push(skb, 10);
|
||||
skb->data[0] = L2TP_SEQUENCE_BIT;
|
||||
skb->data[6] = opt->xmit_sequence >> 8;
|
||||
skb->data[7] = opt->xmit_sequence;
|
||||
skb->data[8] = 0;
|
||||
skb->data[9] = 0;
|
||||
opt->xmit_sequence++;
|
||||
} else {
|
||||
skb_push(skb, 6);
|
||||
skb->data[0] = 0;
|
||||
}
|
||||
skb->data[1] = L2TP_VERSION;
|
||||
unaligned(&skb->data[2])->u32 = opt->remote;
|
||||
|
||||
/* Now send the packet via the delivery queue. */
|
||||
skb_set_owner_w(skb, sk_udp);
|
||||
skb_queue_tail(&delivery_queue, skb);
|
||||
schedule_work(&delivery_work);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
static struct ppp_channel_ops pppolac_channel_ops = {
|
||||
.start_xmit = pppolac_xmit,
|
||||
};
|
||||
|
||||
static int pppolac_connect(struct socket *sock, struct sockaddr *useraddr,
|
||||
int addrlen, int flags)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
struct pppox_sock *po = pppox_sk(sk);
|
||||
struct sockaddr_pppolac *addr = (struct sockaddr_pppolac *)useraddr;
|
||||
struct socket *sock_udp = NULL;
|
||||
struct sock *sk_udp;
|
||||
int error;
|
||||
|
||||
if (addrlen != sizeof(struct sockaddr_pppolac) ||
|
||||
!addr->local.tunnel || !addr->local.session ||
|
||||
!addr->remote.tunnel || !addr->remote.session) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
lock_sock(sk);
|
||||
error = -EALREADY;
|
||||
if (sk->sk_state != PPPOX_NONE)
|
||||
goto out;
|
||||
|
||||
sock_udp = sockfd_lookup(addr->udp_socket, &error);
|
||||
if (!sock_udp)
|
||||
goto out;
|
||||
sk_udp = sock_udp->sk;
|
||||
lock_sock(sk_udp);
|
||||
|
||||
/* Remove this check when IPv6 supports UDP encapsulation. */
|
||||
error = -EAFNOSUPPORT;
|
||||
if (sk_udp->sk_family != AF_INET)
|
||||
goto out;
|
||||
error = -EPROTONOSUPPORT;
|
||||
if (sk_udp->sk_protocol != IPPROTO_UDP)
|
||||
goto out;
|
||||
error = -EDESTADDRREQ;
|
||||
if (sk_udp->sk_state != TCP_ESTABLISHED)
|
||||
goto out;
|
||||
error = -EBUSY;
|
||||
if (udp_sk(sk_udp)->encap_type || sk_udp->sk_user_data)
|
||||
goto out;
|
||||
if (!sk_udp->sk_bound_dev_if) {
|
||||
struct dst_entry *dst = sk_dst_get(sk_udp);
|
||||
error = -ENODEV;
|
||||
if (!dst)
|
||||
goto out;
|
||||
sk_udp->sk_bound_dev_if = dst->dev->ifindex;
|
||||
dst_release(dst);
|
||||
}
|
||||
|
||||
po->chan.hdrlen = 12;
|
||||
po->chan.private = sk_udp;
|
||||
po->chan.ops = &pppolac_channel_ops;
|
||||
po->chan.mtu = PPP_MRU - 80;
|
||||
po->proto.lac.local = unaligned(&addr->local)->u32;
|
||||
po->proto.lac.remote = unaligned(&addr->remote)->u32;
|
||||
atomic_set(&po->proto.lac.sequencing, 1);
|
||||
po->proto.lac.backlog_rcv = sk_udp->sk_backlog_rcv;
|
||||
|
||||
error = ppp_register_channel(&po->chan);
|
||||
if (error)
|
||||
goto out;
|
||||
|
||||
sk->sk_state = PPPOX_CONNECTED;
|
||||
udp_sk(sk_udp)->encap_type = UDP_ENCAP_L2TPINUDP;
|
||||
udp_sk(sk_udp)->encap_rcv = pppolac_recv;
|
||||
sk_udp->sk_backlog_rcv = pppolac_recv_core;
|
||||
sk_udp->sk_user_data = sk;
|
||||
out:
|
||||
if (sock_udp) {
|
||||
release_sock(sk_udp);
|
||||
if (error)
|
||||
sockfd_put(sock_udp);
|
||||
}
|
||||
release_sock(sk);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int pppolac_release(struct socket *sock)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
|
||||
if (!sk)
|
||||
return 0;
|
||||
|
||||
lock_sock(sk);
|
||||
if (sock_flag(sk, SOCK_DEAD)) {
|
||||
release_sock(sk);
|
||||
return -EBADF;
|
||||
}
|
||||
|
||||
if (sk->sk_state != PPPOX_NONE) {
|
||||
struct sock *sk_udp = (struct sock *)pppox_sk(sk)->chan.private;
|
||||
lock_sock(sk_udp);
|
||||
skb_queue_purge(&sk->sk_receive_queue);
|
||||
pppox_unbind_sock(sk);
|
||||
udp_sk(sk_udp)->encap_type = 0;
|
||||
udp_sk(sk_udp)->encap_rcv = NULL;
|
||||
sk_udp->sk_backlog_rcv = pppox_sk(sk)->proto.lac.backlog_rcv;
|
||||
sk_udp->sk_user_data = NULL;
|
||||
release_sock(sk_udp);
|
||||
sockfd_put(sk_udp->sk_socket);
|
||||
}
|
||||
|
||||
sock_orphan(sk);
|
||||
sock->sk = NULL;
|
||||
release_sock(sk);
|
||||
sock_put(sk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
static struct proto pppolac_proto = {
|
||||
.name = "PPPOLAC",
|
||||
.owner = THIS_MODULE,
|
||||
.obj_size = sizeof(struct pppox_sock),
|
||||
};
|
||||
|
||||
static struct proto_ops pppolac_proto_ops = {
|
||||
.family = PF_PPPOX,
|
||||
.owner = THIS_MODULE,
|
||||
.release = pppolac_release,
|
||||
.bind = sock_no_bind,
|
||||
.connect = pppolac_connect,
|
||||
.socketpair = sock_no_socketpair,
|
||||
.accept = sock_no_accept,
|
||||
.getname = sock_no_getname,
|
||||
.poll = sock_no_poll,
|
||||
.ioctl = pppox_ioctl,
|
||||
.listen = sock_no_listen,
|
||||
.shutdown = sock_no_shutdown,
|
||||
.setsockopt = sock_no_setsockopt,
|
||||
.getsockopt = sock_no_getsockopt,
|
||||
.sendmsg = sock_no_sendmsg,
|
||||
.recvmsg = sock_no_recvmsg,
|
||||
.mmap = sock_no_mmap,
|
||||
};
|
||||
|
||||
static int pppolac_create(struct net *net, struct socket *sock, int kern)
|
||||
{
|
||||
struct sock *sk;
|
||||
|
||||
sk = sk_alloc(net, PF_PPPOX, GFP_KERNEL, &pppolac_proto, kern);
|
||||
if (!sk)
|
||||
return -ENOMEM;
|
||||
|
||||
sock_init_data(sock, sk);
|
||||
sock->state = SS_UNCONNECTED;
|
||||
sock->ops = &pppolac_proto_ops;
|
||||
sk->sk_protocol = PX_PROTO_OLAC;
|
||||
sk->sk_state = PPPOX_NONE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
static struct pppox_proto pppolac_pppox_proto = {
|
||||
.create = pppolac_create,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int __init pppolac_init(void)
|
||||
{
|
||||
int error;
|
||||
|
||||
error = proto_register(&pppolac_proto, 0);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
error = register_pppox_proto(PX_PROTO_OLAC, &pppolac_pppox_proto);
|
||||
if (error)
|
||||
proto_unregister(&pppolac_proto);
|
||||
else
|
||||
skb_queue_head_init(&delivery_queue);
|
||||
return error;
|
||||
}
|
||||
|
||||
static void __exit pppolac_exit(void)
|
||||
{
|
||||
unregister_pppox_proto(PX_PROTO_OLAC);
|
||||
proto_unregister(&pppolac_proto);
|
||||
}
|
||||
|
||||
module_init(pppolac_init);
|
||||
module_exit(pppolac_exit);
|
||||
|
||||
MODULE_DESCRIPTION("PPP on L2TP Access Concentrator (PPPoLAC)");
|
||||
MODULE_AUTHOR("Chia-chi Yeh <chiachi@android.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
428
drivers/net/ppp/pppopns.c
Normal file
428
drivers/net/ppp/pppopns.c
Normal file
|
|
@ -0,0 +1,428 @@
|
|||
/* drivers/net/pppopns.c
|
||||
*
|
||||
* Driver for PPP on PPTP Network Server / PPPoPNS Socket (RFC 2637)
|
||||
*
|
||||
* Copyright (C) 2009 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* This driver handles PPTP data packets between a RAW socket and a PPP channel.
|
||||
* The socket is created in the kernel space and connected to the same address
|
||||
* of the control socket. Outgoing packets are always sent with sequences but
|
||||
* without acknowledgements. Incoming packets with sequences are reordered
|
||||
* within a sliding window of one second. Currently reordering only happens when
|
||||
* a packet is received. It is done for simplicity since no additional locks or
|
||||
* threads are required. This driver should work on both IPv4 and IPv6. */
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/net.h>
|
||||
#include <linux/ppp_defs.h>
|
||||
#include <linux/if.h>
|
||||
#include <linux/if_ppp.h>
|
||||
#include <linux/if_pppox.h>
|
||||
#include <linux/ppp_channel.h>
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
#define GRE_HEADER_SIZE 8
|
||||
|
||||
#define PPTP_GRE_BITS htons(0x2001)
|
||||
#define PPTP_GRE_BITS_MASK htons(0xEF7F)
|
||||
#define PPTP_GRE_SEQ_BIT htons(0x1000)
|
||||
#define PPTP_GRE_ACK_BIT htons(0x0080)
|
||||
#define PPTP_GRE_TYPE htons(0x880B)
|
||||
|
||||
#define PPP_ADDR 0xFF
|
||||
#define PPP_CTRL 0x03
|
||||
|
||||
struct header {
|
||||
__u16 bits;
|
||||
__u16 type;
|
||||
__u16 length;
|
||||
__u16 call;
|
||||
__u32 sequence;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct meta {
|
||||
__u32 sequence;
|
||||
__u32 timestamp;
|
||||
};
|
||||
|
||||
static inline struct meta *skb_meta(struct sk_buff *skb)
|
||||
{
|
||||
return (struct meta *)skb->cb;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
static int pppopns_recv_core(struct sock *sk_raw, struct sk_buff *skb)
|
||||
{
|
||||
struct sock *sk = (struct sock *)sk_raw->sk_user_data;
|
||||
struct pppopns_opt *opt = &pppox_sk(sk)->proto.pns;
|
||||
struct meta *meta = skb_meta(skb);
|
||||
__u32 now = jiffies;
|
||||
struct header *hdr;
|
||||
|
||||
/* Skip transport header */
|
||||
skb_pull(skb, skb_transport_header(skb) - skb->data);
|
||||
|
||||
/* Drop the packet if GRE header is missing. */
|
||||
if (skb->len < GRE_HEADER_SIZE)
|
||||
goto drop;
|
||||
hdr = (struct header *)skb->data;
|
||||
|
||||
/* Check the header. */
|
||||
if (hdr->type != PPTP_GRE_TYPE || hdr->call != opt->local ||
|
||||
(hdr->bits & PPTP_GRE_BITS_MASK) != PPTP_GRE_BITS)
|
||||
goto drop;
|
||||
|
||||
/* Skip all fields including optional ones. */
|
||||
if (!skb_pull(skb, GRE_HEADER_SIZE +
|
||||
(hdr->bits & PPTP_GRE_SEQ_BIT ? 4 : 0) +
|
||||
(hdr->bits & PPTP_GRE_ACK_BIT ? 4 : 0)))
|
||||
goto drop;
|
||||
|
||||
/* Check the length. */
|
||||
if (skb->len != ntohs(hdr->length))
|
||||
goto drop;
|
||||
|
||||
/* Check the sequence if it is present. */
|
||||
if (hdr->bits & PPTP_GRE_SEQ_BIT) {
|
||||
meta->sequence = ntohl(hdr->sequence);
|
||||
if ((__s32)(meta->sequence - opt->recv_sequence) < 0)
|
||||
goto drop;
|
||||
}
|
||||
|
||||
/* Skip PPP address and control if they are present. */
|
||||
if (skb->len >= 2 && skb->data[0] == PPP_ADDR &&
|
||||
skb->data[1] == PPP_CTRL)
|
||||
skb_pull(skb, 2);
|
||||
|
||||
/* Fix PPP protocol if it is compressed. */
|
||||
if (skb->len >= 1 && skb->data[0] & 1)
|
||||
skb_push(skb, 1)[0] = 0;
|
||||
|
||||
/* Drop the packet if PPP protocol is missing. */
|
||||
if (skb->len < 2)
|
||||
goto drop;
|
||||
|
||||
/* Perform reordering if sequencing is enabled. */
|
||||
if (hdr->bits & PPTP_GRE_SEQ_BIT) {
|
||||
struct sk_buff *skb1;
|
||||
|
||||
/* Insert the packet into receive queue in order. */
|
||||
skb_set_owner_r(skb, sk);
|
||||
skb_queue_walk(&sk->sk_receive_queue, skb1) {
|
||||
struct meta *meta1 = skb_meta(skb1);
|
||||
__s32 order = meta->sequence - meta1->sequence;
|
||||
if (order == 0)
|
||||
goto drop;
|
||||
if (order < 0) {
|
||||
meta->timestamp = meta1->timestamp;
|
||||
skb_insert(skb1, skb, &sk->sk_receive_queue);
|
||||
skb = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (skb) {
|
||||
meta->timestamp = now;
|
||||
skb_queue_tail(&sk->sk_receive_queue, skb);
|
||||
}
|
||||
|
||||
/* Remove packets from receive queue as long as
|
||||
* 1. the receive buffer is full,
|
||||
* 2. they are queued longer than one second, or
|
||||
* 3. there are no missing packets before them. */
|
||||
skb_queue_walk_safe(&sk->sk_receive_queue, skb, skb1) {
|
||||
meta = skb_meta(skb);
|
||||
if (atomic_read(&sk->sk_rmem_alloc) < sk->sk_rcvbuf &&
|
||||
now - meta->timestamp < HZ &&
|
||||
meta->sequence != opt->recv_sequence)
|
||||
break;
|
||||
skb_unlink(skb, &sk->sk_receive_queue);
|
||||
opt->recv_sequence = meta->sequence + 1;
|
||||
skb_orphan(skb);
|
||||
ppp_input(&pppox_sk(sk)->chan, skb);
|
||||
}
|
||||
return NET_RX_SUCCESS;
|
||||
}
|
||||
|
||||
/* Flush receive queue if sequencing is disabled. */
|
||||
skb_queue_purge(&sk->sk_receive_queue);
|
||||
skb_orphan(skb);
|
||||
ppp_input(&pppox_sk(sk)->chan, skb);
|
||||
return NET_RX_SUCCESS;
|
||||
drop:
|
||||
kfree_skb(skb);
|
||||
return NET_RX_DROP;
|
||||
}
|
||||
|
||||
static void pppopns_recv(struct sock *sk_raw)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
while ((skb = skb_dequeue(&sk_raw->sk_receive_queue))) {
|
||||
sock_hold(sk_raw);
|
||||
sk_receive_skb(sk_raw, skb, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static struct sk_buff_head delivery_queue;
|
||||
|
||||
static void pppopns_xmit_core(struct work_struct *delivery_work)
|
||||
{
|
||||
mm_segment_t old_fs = get_fs();
|
||||
struct sk_buff *skb;
|
||||
|
||||
set_fs(KERNEL_DS);
|
||||
while ((skb = skb_dequeue(&delivery_queue))) {
|
||||
struct sock *sk_raw = skb->sk;
|
||||
struct kvec iov = {.iov_base = skb->data, .iov_len = skb->len};
|
||||
struct msghdr msg = {
|
||||
.msg_iov = (struct iovec *)&iov,
|
||||
.msg_iovlen = 1,
|
||||
.msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT,
|
||||
};
|
||||
sk_raw->sk_prot->sendmsg(sk_raw, &msg, skb->len);
|
||||
kfree_skb(skb);
|
||||
}
|
||||
set_fs(old_fs);
|
||||
}
|
||||
|
||||
static DECLARE_WORK(delivery_work, pppopns_xmit_core);
|
||||
|
||||
static int pppopns_xmit(struct ppp_channel *chan, struct sk_buff *skb)
|
||||
{
|
||||
struct sock *sk_raw = (struct sock *)chan->private;
|
||||
struct pppopns_opt *opt = &pppox_sk(sk_raw->sk_user_data)->proto.pns;
|
||||
struct header *hdr;
|
||||
__u16 length;
|
||||
|
||||
/* Install PPP address and control. */
|
||||
skb_push(skb, 2);
|
||||
skb->data[0] = PPP_ADDR;
|
||||
skb->data[1] = PPP_CTRL;
|
||||
length = skb->len;
|
||||
|
||||
/* Install PPTP GRE header. */
|
||||
hdr = (struct header *)skb_push(skb, 12);
|
||||
hdr->bits = PPTP_GRE_BITS | PPTP_GRE_SEQ_BIT;
|
||||
hdr->type = PPTP_GRE_TYPE;
|
||||
hdr->length = htons(length);
|
||||
hdr->call = opt->remote;
|
||||
hdr->sequence = htonl(opt->xmit_sequence);
|
||||
opt->xmit_sequence++;
|
||||
|
||||
/* Now send the packet via the delivery queue. */
|
||||
skb_set_owner_w(skb, sk_raw);
|
||||
skb_queue_tail(&delivery_queue, skb);
|
||||
schedule_work(&delivery_work);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
static struct ppp_channel_ops pppopns_channel_ops = {
|
||||
.start_xmit = pppopns_xmit,
|
||||
};
|
||||
|
||||
static int pppopns_connect(struct socket *sock, struct sockaddr *useraddr,
|
||||
int addrlen, int flags)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
struct pppox_sock *po = pppox_sk(sk);
|
||||
struct sockaddr_pppopns *addr = (struct sockaddr_pppopns *)useraddr;
|
||||
struct sockaddr_storage ss;
|
||||
struct socket *sock_tcp = NULL;
|
||||
struct socket *sock_raw = NULL;
|
||||
struct sock *sk_tcp;
|
||||
struct sock *sk_raw;
|
||||
int error;
|
||||
|
||||
if (addrlen != sizeof(struct sockaddr_pppopns))
|
||||
return -EINVAL;
|
||||
|
||||
lock_sock(sk);
|
||||
error = -EALREADY;
|
||||
if (sk->sk_state != PPPOX_NONE)
|
||||
goto out;
|
||||
|
||||
sock_tcp = sockfd_lookup(addr->tcp_socket, &error);
|
||||
if (!sock_tcp)
|
||||
goto out;
|
||||
sk_tcp = sock_tcp->sk;
|
||||
error = -EPROTONOSUPPORT;
|
||||
if (sk_tcp->sk_protocol != IPPROTO_TCP)
|
||||
goto out;
|
||||
addrlen = sizeof(struct sockaddr_storage);
|
||||
error = kernel_getpeername(sock_tcp, (struct sockaddr *)&ss, &addrlen);
|
||||
if (error)
|
||||
goto out;
|
||||
if (!sk_tcp->sk_bound_dev_if) {
|
||||
struct dst_entry *dst = sk_dst_get(sk_tcp);
|
||||
error = -ENODEV;
|
||||
if (!dst)
|
||||
goto out;
|
||||
sk_tcp->sk_bound_dev_if = dst->dev->ifindex;
|
||||
dst_release(dst);
|
||||
}
|
||||
|
||||
error = sock_create(ss.ss_family, SOCK_RAW, IPPROTO_GRE, &sock_raw);
|
||||
if (error)
|
||||
goto out;
|
||||
sk_raw = sock_raw->sk;
|
||||
sk_raw->sk_bound_dev_if = sk_tcp->sk_bound_dev_if;
|
||||
error = kernel_connect(sock_raw, (struct sockaddr *)&ss, addrlen, 0);
|
||||
if (error)
|
||||
goto out;
|
||||
|
||||
po->chan.hdrlen = 14;
|
||||
po->chan.private = sk_raw;
|
||||
po->chan.ops = &pppopns_channel_ops;
|
||||
po->chan.mtu = PPP_MRU - 80;
|
||||
po->proto.pns.local = addr->local;
|
||||
po->proto.pns.remote = addr->remote;
|
||||
po->proto.pns.data_ready = sk_raw->sk_data_ready;
|
||||
po->proto.pns.backlog_rcv = sk_raw->sk_backlog_rcv;
|
||||
|
||||
error = ppp_register_channel(&po->chan);
|
||||
if (error)
|
||||
goto out;
|
||||
|
||||
sk->sk_state = PPPOX_CONNECTED;
|
||||
lock_sock(sk_raw);
|
||||
sk_raw->sk_data_ready = pppopns_recv;
|
||||
sk_raw->sk_backlog_rcv = pppopns_recv_core;
|
||||
sk_raw->sk_user_data = sk;
|
||||
release_sock(sk_raw);
|
||||
out:
|
||||
if (sock_tcp)
|
||||
sockfd_put(sock_tcp);
|
||||
if (error && sock_raw)
|
||||
sock_release(sock_raw);
|
||||
release_sock(sk);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int pppopns_release(struct socket *sock)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
|
||||
if (!sk)
|
||||
return 0;
|
||||
|
||||
lock_sock(sk);
|
||||
if (sock_flag(sk, SOCK_DEAD)) {
|
||||
release_sock(sk);
|
||||
return -EBADF;
|
||||
}
|
||||
|
||||
if (sk->sk_state != PPPOX_NONE) {
|
||||
struct sock *sk_raw = (struct sock *)pppox_sk(sk)->chan.private;
|
||||
lock_sock(sk_raw);
|
||||
skb_queue_purge(&sk->sk_receive_queue);
|
||||
pppox_unbind_sock(sk);
|
||||
sk_raw->sk_data_ready = pppox_sk(sk)->proto.pns.data_ready;
|
||||
sk_raw->sk_backlog_rcv = pppox_sk(sk)->proto.pns.backlog_rcv;
|
||||
sk_raw->sk_user_data = NULL;
|
||||
release_sock(sk_raw);
|
||||
sock_release(sk_raw->sk_socket);
|
||||
}
|
||||
|
||||
sock_orphan(sk);
|
||||
sock->sk = NULL;
|
||||
release_sock(sk);
|
||||
sock_put(sk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
static struct proto pppopns_proto = {
|
||||
.name = "PPPOPNS",
|
||||
.owner = THIS_MODULE,
|
||||
.obj_size = sizeof(struct pppox_sock),
|
||||
};
|
||||
|
||||
static struct proto_ops pppopns_proto_ops = {
|
||||
.family = PF_PPPOX,
|
||||
.owner = THIS_MODULE,
|
||||
.release = pppopns_release,
|
||||
.bind = sock_no_bind,
|
||||
.connect = pppopns_connect,
|
||||
.socketpair = sock_no_socketpair,
|
||||
.accept = sock_no_accept,
|
||||
.getname = sock_no_getname,
|
||||
.poll = sock_no_poll,
|
||||
.ioctl = pppox_ioctl,
|
||||
.listen = sock_no_listen,
|
||||
.shutdown = sock_no_shutdown,
|
||||
.setsockopt = sock_no_setsockopt,
|
||||
.getsockopt = sock_no_getsockopt,
|
||||
.sendmsg = sock_no_sendmsg,
|
||||
.recvmsg = sock_no_recvmsg,
|
||||
.mmap = sock_no_mmap,
|
||||
};
|
||||
|
||||
static int pppopns_create(struct net *net, struct socket *sock, int kern)
|
||||
{
|
||||
struct sock *sk;
|
||||
|
||||
sk = sk_alloc(net, PF_PPPOX, GFP_KERNEL, &pppopns_proto, kern);
|
||||
if (!sk)
|
||||
return -ENOMEM;
|
||||
|
||||
sock_init_data(sock, sk);
|
||||
sock->state = SS_UNCONNECTED;
|
||||
sock->ops = &pppopns_proto_ops;
|
||||
sk->sk_protocol = PX_PROTO_OPNS;
|
||||
sk->sk_state = PPPOX_NONE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
static struct pppox_proto pppopns_pppox_proto = {
|
||||
.create = pppopns_create,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int __init pppopns_init(void)
|
||||
{
|
||||
int error;
|
||||
|
||||
error = proto_register(&pppopns_proto, 0);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
error = register_pppox_proto(PX_PROTO_OPNS, &pppopns_pppox_proto);
|
||||
if (error)
|
||||
proto_unregister(&pppopns_proto);
|
||||
else
|
||||
skb_queue_head_init(&delivery_queue);
|
||||
return error;
|
||||
}
|
||||
|
||||
static void __exit pppopns_exit(void)
|
||||
{
|
||||
unregister_pppox_proto(PX_PROTO_OPNS);
|
||||
proto_unregister(&pppopns_proto);
|
||||
}
|
||||
|
||||
module_init(pppopns_init);
|
||||
module_exit(pppopns_exit);
|
||||
|
||||
MODULE_DESCRIPTION("PPP on PPTP Network Server (PPPoPNS)");
|
||||
MODULE_AUTHOR("Chia-chi Yeh <chiachi@android.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -1886,6 +1886,12 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd,
|
|||
int le;
|
||||
int ret;
|
||||
|
||||
#ifdef CONFIG_ANDROID_PARANOID_NETWORK
|
||||
if (cmd != TUNGETIFF && !capable(CAP_NET_ADMIN)) {
|
||||
return -EPERM;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (cmd == TUNSETIFF || cmd == TUNSETQUEUE || _IOC_TYPE(cmd) == 0x89) {
|
||||
if (copy_from_user(&ifr, argp, ifreq_len))
|
||||
return -EFAULT;
|
||||
|
|
|
|||
|
|
@ -938,36 +938,66 @@ int __init early_init_dt_scan_memory(unsigned long node, const char *uname,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert configs to something easy to use in C code
|
||||
*/
|
||||
#if defined(CONFIG_CMDLINE_FORCE)
|
||||
static const int overwrite_incoming_cmdline = 1;
|
||||
static const int read_dt_cmdline;
|
||||
static const int concat_cmdline;
|
||||
#elif defined(CONFIG_CMDLINE_EXTEND)
|
||||
static const int overwrite_incoming_cmdline;
|
||||
static const int read_dt_cmdline = 1;
|
||||
static const int concat_cmdline = 1;
|
||||
#else /* CMDLINE_FROM_BOOTLOADER */
|
||||
static const int overwrite_incoming_cmdline;
|
||||
static const int read_dt_cmdline = 1;
|
||||
static const int concat_cmdline;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_CMDLINE
|
||||
static const char *config_cmdline = CONFIG_CMDLINE;
|
||||
#else
|
||||
static const char *config_cmdline = "";
|
||||
#endif
|
||||
|
||||
int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,
|
||||
int depth, void *data)
|
||||
{
|
||||
int l;
|
||||
const char *p;
|
||||
int l = 0;
|
||||
const char *p = NULL;
|
||||
char *cmdline = data;
|
||||
|
||||
pr_debug("search \"chosen\", depth: %d, uname: %s\n", depth, uname);
|
||||
|
||||
if (depth != 1 || !data ||
|
||||
if (depth != 1 || !cmdline ||
|
||||
(strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))
|
||||
return 0;
|
||||
|
||||
early_init_dt_check_for_initrd(node);
|
||||
|
||||
/* Retrieve command line */
|
||||
p = of_get_flat_dt_prop(node, "bootargs", &l);
|
||||
if (p != NULL && l > 0)
|
||||
strlcpy(data, p, min((int)l, COMMAND_LINE_SIZE));
|
||||
/* Put CONFIG_CMDLINE in if forced or if data had nothing in it to start */
|
||||
if (overwrite_incoming_cmdline || !cmdline[0])
|
||||
strlcpy(cmdline, config_cmdline, COMMAND_LINE_SIZE);
|
||||
|
||||
/*
|
||||
* CONFIG_CMDLINE is meant to be a default in case nothing else
|
||||
* managed to set the command line, unless CONFIG_CMDLINE_FORCE
|
||||
* is set in which case we override whatever was found earlier.
|
||||
*/
|
||||
#ifdef CONFIG_CMDLINE
|
||||
#ifndef CONFIG_CMDLINE_FORCE
|
||||
if (!((char *)data)[0])
|
||||
#endif
|
||||
strlcpy(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
|
||||
#endif /* CONFIG_CMDLINE */
|
||||
/* Retrieve command line unless forcing */
|
||||
if (read_dt_cmdline)
|
||||
p = of_get_flat_dt_prop(node, "bootargs", &l);
|
||||
|
||||
if (p != NULL && l > 0) {
|
||||
if (concat_cmdline) {
|
||||
int cmdline_len;
|
||||
int copy_len;
|
||||
strlcat(cmdline, " ", COMMAND_LINE_SIZE);
|
||||
cmdline_len = strlen(cmdline);
|
||||
copy_len = COMMAND_LINE_SIZE - cmdline_len - 1;
|
||||
copy_len = min((int)l, copy_len);
|
||||
strncpy(cmdline + cmdline_len, p, copy_len);
|
||||
cmdline[cmdline_len + copy_len] = '\0';
|
||||
} else {
|
||||
strlcpy(cmdline, p, min((int)l, COMMAND_LINE_SIZE));
|
||||
}
|
||||
}
|
||||
|
||||
pr_debug("Command line is: %s\n", (char*)data);
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue