mirror of
https://github.com/torvalds/linux.git
synced 2026-06-08 14:42:37 +02:00
Merge branch 'experimental/android-3.10' of https://android.googlesource.com/kernel/common into linux-linaro-lsk-android
Conflicts: arch/arm/include/asm/smp.h arch/arm/kernel/smp.c kernel/futex.c
This commit is contained in:
commit
0b37fbf750
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
|
||||
|
|
@ -598,6 +598,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
|
||||
=============================================
|
||||
|
||||
|
|
|
|||
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);
|
||||
|
|
@ -2013,6 +2013,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.
|
||||
|
|
|
|||
13
android/configs/README
Normal file
13
android/configs/README
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
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.
|
||||
|
||||
136
android/configs/android-base.cfg
Normal file
136
android/configs/android-base.cfg
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
# CONFIG_INET_LRO is not set
|
||||
# CONFIG_MODULES is not set
|
||||
# CONFIG_OABI_COMPAT is not set
|
||||
CONFIG_ANDROID=y
|
||||
CONFIG_ANDROID_BINDER_IPC=y
|
||||
CONFIG_ANDROID_INTF_ALARM_DEV=y
|
||||
CONFIG_ANDROID_LOGGER=y
|
||||
CONFIG_ANDROID_LOW_MEMORY_KILLER=y
|
||||
CONFIG_ASHMEM=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_DM_CRYPT=y
|
||||
CONFIG_EMBEDDED=y
|
||||
CONFIG_EXPERIMENTAL=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_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_MIP6=y
|
||||
CONFIG_IPV6_MULTIPLE_TABLES=y
|
||||
CONFIG_IPV6_OPTIMISTIC_DAD=y
|
||||
CONFIG_IPV6_PRIVACY=y
|
||||
CONFIG_IPV6_ROUTER_PREF=y
|
||||
CONFIG_IP_ADVANCED_ROUTER=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_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_MARK=y
|
||||
CONFIG_NETFILTER_XT_TARGET_NFLOG=y
|
||||
CONFIG_NETFILTER_XT_TARGET_NFQUEUE=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_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_STAGING=y
|
||||
CONFIG_SWITCH=y
|
||||
CONFIG_SYNC=y
|
||||
CONFIG_SYSVIPC=y
|
||||
CONFIG_TUN=y
|
||||
CONFIG_UNIX=y
|
||||
CONFIG_USB_GADGET=y
|
||||
CONFIG_USB_G_ANDROID=y
|
||||
CONFIG_USB_OTG_WAKELOCK=y
|
||||
CONFIG_XFRM_USER=y
|
||||
118
android/configs/android-recommended.cfg
Normal file
118
android/configs/android-recommended.cfg
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
CONFIG_PANIC_TIMEOUT=5
|
||||
CONFIG_KALLSYMS_ALL=y
|
||||
CONFIG_PERF_EVENTS=y
|
||||
CONFIG_COMPACTION=y
|
||||
# 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_RAM_CONSOLE=y
|
||||
CONFIG_ANDROID_TIMED_GPIO=y
|
||||
CONFIG_BACKLIGHT_LCD_SUPPORT=y
|
||||
CONFIG_BATTERY_ANDROID=y
|
||||
CONFIG_BLK_DEV_LOOP=y
|
||||
CONFIG_BLK_DEV_RAM=y
|
||||
CONFIG_BLK_DEV_RAM_SIZE=8192
|
||||
CONFIG_DM_UEVENT=y
|
||||
CONFIG_DRAGONRISE_FF=y
|
||||
CONFIG_EXT4_FS=y
|
||||
CONFIG_EXT4_FS_SECURITY=y
|
||||
CONFIG_FUSE_FS=y
|
||||
CONFIG_GREENASIA_FF=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_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_PANTHERLORD_FF=y
|
||||
CONFIG_PM_DEBUG=y
|
||||
CONFIG_PM_RUNTIME=y
|
||||
CONFIG_PM_WAKELOCKS_LIMIT=0
|
||||
CONFIG_POWER_SUPPLY=y
|
||||
CONFIG_SCHEDSTATS=y
|
||||
CONFIG_SCHED_TRACER=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_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
|
||||
|
|
@ -1928,6 +1928,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"
|
||||
|
|
@ -1957,6 +1966,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
|
||||
|
|
|
|||
|
|
@ -63,6 +63,27 @@ config DEBUG_USER
|
|||
8 - SIGSEGV faults
|
||||
16 - SIGBUS faults
|
||||
|
||||
config DEBUG_RODATA
|
||||
bool "Write protect kernel text section"
|
||||
default n
|
||||
depends on DEBUG_KERNEL && MMU
|
||||
---help---
|
||||
Mark the kernel text section as write-protected in the pagetables,
|
||||
in order to catch accidental (and incorrect) writes to such const
|
||||
data. This will cause the size of the kernel, plus up to 4MB, to
|
||||
be mapped as pages instead of sections, which will increase TLB
|
||||
pressure.
|
||||
If in doubt, say "N".
|
||||
|
||||
config DEBUG_RODATA_TEST
|
||||
bool "Testcase for the DEBUG_RODATA feature"
|
||||
depends on DEBUG_RODATA
|
||||
default n
|
||||
---help---
|
||||
This option enables a testcase for the DEBUG_RODATA
|
||||
feature.
|
||||
If in doubt, say "N"
|
||||
|
||||
# These options are only for real kernel hackers who want to get their hands dirty.
|
||||
config DEBUG_LL
|
||||
bool "Kernel low-level debugging functions (read help!)"
|
||||
|
|
|
|||
|
|
@ -264,6 +264,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
|
||||
|
|
@ -295,6 +297,9 @@ zinstall uinstall install: vmlinux
|
|||
dtbs: scripts
|
||||
$(Q)$(MAKE) $(build)=$(boot)/dts MACHINE=$(MACHINE) dtbs
|
||||
|
||||
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
|
||||
|
|
@ -27,6 +27,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 +63,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),)
|
||||
|
|
|
|||
|
|
@ -717,6 +717,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
|
||||
|
|
|
|||
|
|
@ -217,13 +217,20 @@ dtb-$(CONFIG_ARCH_VT8500) += vt8500-bv07.dtb \
|
|||
wm8850-w70v2.dtb
|
||||
dtb-$(CONFIG_ARCH_ZYNQ) += zynq-zc702.dtb
|
||||
|
||||
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
|
||||
targets += $(dtb-y)
|
||||
targets += $(DTB_LIST)
|
||||
endif
|
||||
|
||||
# *.dtb used to be generated in the directory above. Clean out the
|
||||
# old build results so people don't accidentally use them.
|
||||
dtbs: $(addprefix $(obj)/, $(dtb-y))
|
||||
dtbs: $(addprefix $(obj)/, $(DTB_LIST))
|
||||
$(Q)rm -f $(obj)/../*.dtb
|
||||
|
||||
clean-files := *.dtb
|
||||
|
|
|
|||
|
|
@ -17,3 +17,53 @@ config SHARP_PARAM
|
|||
|
||||
config SHARP_SCOOP
|
||||
bool
|
||||
|
||||
config FIQ_GLUE
|
||||
bool
|
||||
select FIQ
|
||||
|
||||
config FIQ_DEBUGGER
|
||||
bool "FIQ Mode Serial Debugger"
|
||||
select FIQ
|
||||
select FIQ_GLUE
|
||||
default n
|
||||
help
|
||||
The FIQ serial debugger can accept commands even when the
|
||||
kernel is unresponsive due to being stuck with interrupts
|
||||
disabled.
|
||||
|
||||
|
||||
config FIQ_DEBUGGER_NO_SLEEP
|
||||
bool "Keep serial debugger active"
|
||||
depends on FIQ_DEBUGGER
|
||||
default n
|
||||
help
|
||||
Enables the serial debugger at boot. Passing
|
||||
fiq_debugger.no_sleep on the kernel commandline will
|
||||
override this config option.
|
||||
|
||||
config FIQ_DEBUGGER_WAKEUP_IRQ_ALWAYS_ON
|
||||
bool "Don't disable wakeup IRQ when debugger is active"
|
||||
depends on FIQ_DEBUGGER
|
||||
default n
|
||||
help
|
||||
Don't disable the wakeup irq when enabling the uart clock. This will
|
||||
cause extra interrupts, but it makes the serial debugger usable with
|
||||
on some MSM radio builds that ignore the uart clock request in power
|
||||
collapse.
|
||||
|
||||
config FIQ_DEBUGGER_CONSOLE
|
||||
bool "Console on FIQ Serial Debugger port"
|
||||
depends on FIQ_DEBUGGER
|
||||
default n
|
||||
help
|
||||
Enables a console so that printk messages are displayed on
|
||||
the debugger serial port as the occur.
|
||||
|
||||
config FIQ_DEBUGGER_CONSOLE_DEFAULT_ENABLE
|
||||
bool "Put the FIQ debugger into console mode by default"
|
||||
depends on FIQ_DEBUGGER_CONSOLE
|
||||
default n
|
||||
help
|
||||
If enabled, this puts the fiq debugger into console mode by default.
|
||||
Otherwise, the fiq debugger will start out in debug mode.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
obj-y += firmware.o
|
||||
|
||||
obj-$(CONFIG_FIQ_DEBUGGER) += fiq_debugger.o
|
||||
obj-$(CONFIG_FIQ_GLUE) += fiq_glue.o fiq_glue_setup.o
|
||||
obj-$(CONFIG_ICST) += icst.o
|
||||
obj-$(CONFIG_SA1111) += sa1111.o
|
||||
obj-$(CONFIG_PCI_HOST_VIA82C505) += via82c505.o
|
||||
|
|
|
|||
1376
arch/arm/common/fiq_debugger.c
Normal file
1376
arch/arm/common/fiq_debugger.c
Normal file
File diff suppressed because it is too large
Load Diff
94
arch/arm/common/fiq_debugger_ringbuf.h
Normal file
94
arch/arm/common/fiq_debugger_ringbuf.h
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* arch/arm/common/fiq_debugger_ringbuf.c
|
||||
*
|
||||
* simple lockless ringbuffer
|
||||
*
|
||||
* 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/slab.h>
|
||||
|
||||
struct fiq_debugger_ringbuf {
|
||||
int len;
|
||||
int head;
|
||||
int tail;
|
||||
u8 buf[];
|
||||
};
|
||||
|
||||
|
||||
static inline struct fiq_debugger_ringbuf *fiq_debugger_ringbuf_alloc(int len)
|
||||
{
|
||||
struct fiq_debugger_ringbuf *rbuf;
|
||||
|
||||
rbuf = kzalloc(sizeof(*rbuf) + len, GFP_KERNEL);
|
||||
if (rbuf == NULL)
|
||||
return NULL;
|
||||
|
||||
rbuf->len = len;
|
||||
rbuf->head = 0;
|
||||
rbuf->tail = 0;
|
||||
smp_mb();
|
||||
|
||||
return rbuf;
|
||||
}
|
||||
|
||||
static inline void fiq_debugger_ringbuf_free(struct fiq_debugger_ringbuf *rbuf)
|
||||
{
|
||||
kfree(rbuf);
|
||||
}
|
||||
|
||||
static inline int fiq_debugger_ringbuf_level(struct fiq_debugger_ringbuf *rbuf)
|
||||
{
|
||||
int level = rbuf->head - rbuf->tail;
|
||||
|
||||
if (level < 0)
|
||||
level = rbuf->len + level;
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
static inline int fiq_debugger_ringbuf_room(struct fiq_debugger_ringbuf *rbuf)
|
||||
{
|
||||
return rbuf->len - fiq_debugger_ringbuf_level(rbuf) - 1;
|
||||
}
|
||||
|
||||
static inline u8
|
||||
fiq_debugger_ringbuf_peek(struct fiq_debugger_ringbuf *rbuf, int i)
|
||||
{
|
||||
return rbuf->buf[(rbuf->tail + i) % rbuf->len];
|
||||
}
|
||||
|
||||
static inline int
|
||||
fiq_debugger_ringbuf_consume(struct fiq_debugger_ringbuf *rbuf, int count)
|
||||
{
|
||||
count = min(count, fiq_debugger_ringbuf_level(rbuf));
|
||||
|
||||
rbuf->tail = (rbuf->tail + count) % rbuf->len;
|
||||
smp_mb();
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static inline int
|
||||
fiq_debugger_ringbuf_push(struct fiq_debugger_ringbuf *rbuf, u8 datum)
|
||||
{
|
||||
if (fiq_debugger_ringbuf_room(rbuf) == 0)
|
||||
return 0;
|
||||
|
||||
rbuf->buf[rbuf->head] = datum;
|
||||
smp_mb();
|
||||
rbuf->head = (rbuf->head + 1) % rbuf->len;
|
||||
smp_mb();
|
||||
|
||||
return 1;
|
||||
}
|
||||
111
arch/arm/common/fiq_glue.S
Normal file
111
arch/arm/common/fiq_glue.S
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* 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 */
|
||||
mrs r12, spsr
|
||||
sub r11, lr, #4
|
||||
subs r10, #1
|
||||
bne nested_fiq
|
||||
|
||||
stmfd sp!, {r11-r12, lr}
|
||||
|
||||
/* 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}
|
||||
add sp, sp, #(7 * 4)
|
||||
ldmfd sp!, {r11-r12, lr}
|
||||
exit_fiq:
|
||||
msr spsr_cxsf, r12
|
||||
add r10, #1
|
||||
movs pc, r11
|
||||
|
||||
nested_fiq:
|
||||
orr r12, r12, #(PSR_F_BIT)
|
||||
b exit_fiq
|
||||
|
||||
fiq_glue_end:
|
||||
|
||||
ENTRY(fiq_glue_setup) /* func, data, sp */
|
||||
mrs r3, cpsr
|
||||
msr cpsr_c, #(FIQ_MODE | PSR_I_BIT | PSR_F_BIT)
|
||||
movs r8, r0
|
||||
mov r9, r1
|
||||
mov sp, r2
|
||||
moveq r10, #0
|
||||
movne r10, #1
|
||||
msr cpsr_c, r3
|
||||
bx lr
|
||||
|
||||
100
arch/arm/common/fiq_glue_setup.c
Normal file
100
arch/arm/common/fiq_glue_setup.c
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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);
|
||||
|
||||
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 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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
if (current_handler->resume)
|
||||
current_handler->resume(current_handler);
|
||||
}
|
||||
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
#include <asm/shmparam.h>
|
||||
#include <asm/cachetype.h>
|
||||
#include <asm/outercache.h>
|
||||
#include <asm/rodata.h>
|
||||
|
||||
#define CACHE_COLOUR(vaddr) ((vaddr & (SHMLBA - 1)) >> PAGE_SHIFT)
|
||||
|
||||
|
|
|
|||
64
arch/arm/include/asm/fiq_debugger.h
Normal file
64
arch/arm/include/asm/fiq_debugger.h
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* arch/arm/include/asm/fiq_debugger.h
|
||||
*
|
||||
* Copyright (C) 2010 Google, Inc.
|
||||
* Author: Colin Cross <ccross@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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _ARCH_ARM_MACH_TEGRA_FIQ_DEBUGGER_H_
|
||||
#define _ARCH_ARM_MACH_TEGRA_FIQ_DEBUGGER_H_
|
||||
|
||||
#include <linux/serial_core.h>
|
||||
|
||||
#define FIQ_DEBUGGER_NO_CHAR NO_POLL_CHAR
|
||||
#define FIQ_DEBUGGER_BREAK 0x00ff0100
|
||||
|
||||
#define FIQ_DEBUGGER_FIQ_IRQ_NAME "fiq"
|
||||
#define FIQ_DEBUGGER_SIGNAL_IRQ_NAME "signal"
|
||||
#define FIQ_DEBUGGER_WAKEUP_IRQ_NAME "wakeup"
|
||||
|
||||
/**
|
||||
* struct fiq_debugger_pdata - fiq debugger platform data
|
||||
* @uart_resume: used to restore uart state right before enabling
|
||||
* the fiq.
|
||||
* @uart_enable: Do the work necessary to communicate with the uart
|
||||
* hw (enable clocks, etc.). This must be ref-counted.
|
||||
* @uart_disable: Do the work necessary to disable the uart hw
|
||||
* (disable clocks, etc.). This must be ref-counted.
|
||||
* @uart_dev_suspend: called during PM suspend, generally not needed
|
||||
* for real fiq mode debugger.
|
||||
* @uart_dev_resume: called during PM resume, generally not needed
|
||||
* for real fiq mode debugger.
|
||||
*/
|
||||
struct fiq_debugger_pdata {
|
||||
int (*uart_init)(struct platform_device *pdev);
|
||||
void (*uart_free)(struct platform_device *pdev);
|
||||
int (*uart_resume)(struct platform_device *pdev);
|
||||
int (*uart_getc)(struct platform_device *pdev);
|
||||
void (*uart_putc)(struct platform_device *pdev, unsigned int c);
|
||||
void (*uart_flush)(struct platform_device *pdev);
|
||||
void (*uart_enable)(struct platform_device *pdev);
|
||||
void (*uart_disable)(struct platform_device *pdev);
|
||||
|
||||
int (*uart_dev_suspend)(struct platform_device *pdev);
|
||||
int (*uart_dev_resume)(struct platform_device *pdev);
|
||||
|
||||
void (*fiq_enable)(struct platform_device *pdev, unsigned int fiq,
|
||||
bool enable);
|
||||
void (*fiq_ack)(struct platform_device *pdev, unsigned int fiq);
|
||||
|
||||
void (*force_irq)(struct platform_device *pdev, unsigned int irq);
|
||||
void (*force_irq_ack)(struct platform_device *pdev, unsigned int irq);
|
||||
};
|
||||
|
||||
#endif
|
||||
30
arch/arm/include/asm/fiq_glue.h
Normal file
30
arch/arm/include/asm/fiq_glue.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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);
|
||||
};
|
||||
|
||||
int fiq_glue_register_handler(struct fiq_glue_handler *handler);
|
||||
|
||||
#ifdef CONFIG_FIQ_GLUE
|
||||
void fiq_glue_resume(void);
|
||||
#else
|
||||
static inline void fiq_glue_resume(void) {}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
#include <linux/threads.h>
|
||||
#include <asm/irq.h>
|
||||
|
||||
#define NR_IPI 7
|
||||
#define NR_IPI 8
|
||||
|
||||
typedef struct {
|
||||
unsigned int __softirq_pending;
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@
|
|||
#define L2X0_STNDBY_MODE_EN (1 << 0)
|
||||
|
||||
/* Registers shifts and masks */
|
||||
#define L2X0_CACHE_ID_REV_MASK (0x3f)
|
||||
#define L2X0_CACHE_ID_PART_MASK (0xf << 6)
|
||||
#define L2X0_CACHE_ID_PART_L210 (1 << 6)
|
||||
#define L2X0_CACHE_ID_PART_L310 (3 << 6)
|
||||
|
|
@ -106,6 +107,8 @@
|
|||
|
||||
#define L2X0_WAY_SIZE_SHIFT 3
|
||||
|
||||
#define REV_PL310_R2P0 4
|
||||
|
||||
#ifndef __ASSEMBLY__
|
||||
extern void __init l2x0_init(void __iomem *base, u32 aux_val, u32 aux_mask);
|
||||
#if defined(CONFIG_CACHE_L2X0) && defined(CONFIG_OF)
|
||||
|
|
|
|||
|
|
@ -17,15 +17,23 @@
|
|||
#define TRACER_ACCESSED_BIT 0
|
||||
#define TRACER_RUNNING_BIT 1
|
||||
#define TRACER_CYCLE_ACC_BIT 2
|
||||
#define TRACER_TRACE_DATA_BIT 3
|
||||
#define TRACER_TIMESTAMP_BIT 4
|
||||
#define TRACER_BRANCHOUTPUT_BIT 5
|
||||
#define TRACER_RETURN_STACK_BIT 6
|
||||
#define TRACER_ACCESSED BIT(TRACER_ACCESSED_BIT)
|
||||
#define TRACER_RUNNING BIT(TRACER_RUNNING_BIT)
|
||||
#define TRACER_CYCLE_ACC BIT(TRACER_CYCLE_ACC_BIT)
|
||||
#define TRACER_TRACE_DATA BIT(TRACER_TRACE_DATA_BIT)
|
||||
#define TRACER_TIMESTAMP BIT(TRACER_TIMESTAMP_BIT)
|
||||
#define TRACER_BRANCHOUTPUT BIT(TRACER_BRANCHOUTPUT_BIT)
|
||||
#define TRACER_RETURN_STACK BIT(TRACER_RETURN_STACK_BIT)
|
||||
|
||||
#define TRACER_TIMEOUT 10000
|
||||
|
||||
#define etm_writel(t, v, x) \
|
||||
(__raw_writel((v), (t)->etm_regs + (x)))
|
||||
#define etm_readl(t, x) (__raw_readl((t)->etm_regs + (x)))
|
||||
#define etm_writel(t, id, v, x) \
|
||||
(__raw_writel((v), (t)->etm_regs[(id)] + (x)))
|
||||
#define etm_readl(t, id, x) (__raw_readl((t)->etm_regs[(id)] + (x)))
|
||||
|
||||
/* CoreSight Management Registers */
|
||||
#define CSMR_LOCKACCESS 0xfb0
|
||||
|
|
@ -43,7 +51,7 @@
|
|||
#define ETMCTRL_POWERDOWN 1
|
||||
#define ETMCTRL_PROGRAM (1 << 10)
|
||||
#define ETMCTRL_PORTSEL (1 << 11)
|
||||
#define ETMCTRL_DO_CONTEXTID (3 << 14)
|
||||
#define ETMCTRL_CONTEXTIDSIZE(x) (((x) & 3) << 14)
|
||||
#define ETMCTRL_PORTMASK1 (7 << 4)
|
||||
#define ETMCTRL_PORTMASK2 (1 << 21)
|
||||
#define ETMCTRL_PORTMASK (ETMCTRL_PORTMASK1 | ETMCTRL_PORTMASK2)
|
||||
|
|
@ -55,9 +63,12 @@
|
|||
#define ETMCTRL_DATA_DO_BOTH (ETMCTRL_DATA_DO_DATA | ETMCTRL_DATA_DO_ADDR)
|
||||
#define ETMCTRL_BRANCH_OUTPUT (1 << 8)
|
||||
#define ETMCTRL_CYCLEACCURATE (1 << 12)
|
||||
#define ETMCTRL_TIMESTAMP_EN (1 << 28)
|
||||
#define ETMCTRL_RETURN_STACK_EN (1 << 29)
|
||||
|
||||
/* ETM configuration code register */
|
||||
#define ETMR_CONFCODE (0x04)
|
||||
#define ETMCCR_ETMIDR_PRESENT BIT(31)
|
||||
|
||||
/* ETM trace start/stop resource control register */
|
||||
#define ETMR_TRACESSCTRL (0x18)
|
||||
|
|
@ -113,10 +124,25 @@
|
|||
#define ETMR_TRACEENCTRL 0x24
|
||||
#define ETMTE_INCLEXCL BIT(24)
|
||||
#define ETMR_TRACEENEVT 0x20
|
||||
#define ETMCTRL_OPTS (ETMCTRL_DO_CPRT | \
|
||||
ETMCTRL_DATA_DO_ADDR | \
|
||||
ETMCTRL_BRANCH_OUTPUT | \
|
||||
ETMCTRL_DO_CONTEXTID)
|
||||
|
||||
#define ETMR_VIEWDATAEVT 0x30
|
||||
#define ETMR_VIEWDATACTRL1 0x34
|
||||
#define ETMR_VIEWDATACTRL2 0x38
|
||||
#define ETMR_VIEWDATACTRL3 0x3c
|
||||
#define ETMVDC3_EXCLONLY BIT(16)
|
||||
|
||||
#define ETMCTRL_OPTS (ETMCTRL_DO_CPRT)
|
||||
|
||||
#define ETMR_ID 0x1e4
|
||||
#define ETMIDR_VERSION(x) (((x) >> 4) & 0xff)
|
||||
#define ETMIDR_VERSION_3_1 0x21
|
||||
#define ETMIDR_VERSION_PFT_1_0 0x30
|
||||
|
||||
#define ETMR_CCE 0x1e8
|
||||
#define ETMCCER_RETURN_STACK_IMPLEMENTED BIT(23)
|
||||
#define ETMCCER_TIMESTAMPING_IMPLEMENTED BIT(22)
|
||||
|
||||
#define ETMR_TRACEIDR 0x200
|
||||
|
||||
/* ETM management registers, "ETM Architecture", 3.5.24 */
|
||||
#define ETMMR_OSLAR 0x300
|
||||
|
|
@ -140,14 +166,16 @@
|
|||
#define ETBFF_TRIGIN BIT(8)
|
||||
#define ETBFF_TRIGEVT BIT(9)
|
||||
#define ETBFF_TRIGFL BIT(10)
|
||||
#define ETBFF_STOPFL BIT(12)
|
||||
|
||||
#define etb_writel(t, v, x) \
|
||||
(__raw_writel((v), (t)->etb_regs + (x)))
|
||||
#define etb_readl(t, x) (__raw_readl((t)->etb_regs + (x)))
|
||||
|
||||
#define etm_lock(t) do { etm_writel((t), 0, CSMR_LOCKACCESS); } while (0)
|
||||
#define etm_unlock(t) \
|
||||
do { etm_writel((t), CS_LAR_KEY, CSMR_LOCKACCESS); } while (0)
|
||||
#define etm_lock(t, id) \
|
||||
do { etm_writel((t), (id), 0, CSMR_LOCKACCESS); } while (0)
|
||||
#define etm_unlock(t, id) \
|
||||
do { etm_writel((t), (id), CS_LAR_KEY, CSMR_LOCKACCESS); } while (0)
|
||||
|
||||
#define etb_lock(t) do { etb_writel((t), 0, CSMR_LOCKACCESS); } while (0)
|
||||
#define etb_unlock(t) \
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ extern void (*handle_arch_irq)(struct pt_regs *);
|
|||
extern void set_handle_irq(void (*handle_irq)(struct pt_regs *));
|
||||
#endif
|
||||
|
||||
void arch_trigger_all_cpu_backtrace(void);
|
||||
#define arch_trigger_all_cpu_backtrace arch_trigger_all_cpu_backtrace
|
||||
|
||||
#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
|
||||
32
arch/arm/include/asm/rodata.h
Normal file
32
arch/arm/include/asm/rodata.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* arch/arm/include/asm/rodata.h
|
||||
*
|
||||
* Copyright (C) 2011 Google, Inc.
|
||||
*
|
||||
* Author: Colin Cross <ccross@android.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#ifndef _ASMARM_RODATA_H
|
||||
#define _ASMARM_RODATA_H
|
||||
|
||||
#ifndef __ASSEMBLY__
|
||||
|
||||
#ifdef CONFIG_DEBUG_RODATA
|
||||
|
||||
int set_memory_rw(unsigned long virt, int numpages);
|
||||
int set_memory_ro(unsigned long virt, int numpages);
|
||||
|
||||
void mark_rodata_ro(void);
|
||||
void set_kernel_text_rw(void);
|
||||
void set_kernel_text_ro(void);
|
||||
#else
|
||||
static inline void set_kernel_text_rw(void) { }
|
||||
static inline void set_kernel_text_ro(void) { }
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -82,6 +82,7 @@ extern void arch_send_call_function_ipi_mask(const struct cpumask *mask);
|
|||
extern void arch_send_wakeup_ipi_mask(const struct cpumask *mask);
|
||||
|
||||
extern int register_ipi_completion(struct completion *completion, int cpu);
|
||||
extern void smp_send_all_cpu_backtrace(void);
|
||||
|
||||
struct smp_operations {
|
||||
#ifdef CONFIG_SMP
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <linux/init.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sysrq.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/clk.h>
|
||||
|
|
@ -37,26 +38,37 @@ MODULE_AUTHOR("Alexander Shishkin");
|
|||
struct tracectx {
|
||||
unsigned int etb_bufsz;
|
||||
void __iomem *etb_regs;
|
||||
void __iomem *etm_regs;
|
||||
void __iomem **etm_regs;
|
||||
int etm_regs_count;
|
||||
unsigned long flags;
|
||||
int ncmppairs;
|
||||
int etm_portsz;
|
||||
int etm_contextid_size;
|
||||
u32 etb_fc;
|
||||
unsigned long range_start;
|
||||
unsigned long range_end;
|
||||
unsigned long data_range_start;
|
||||
unsigned long data_range_end;
|
||||
bool dump_initial_etb;
|
||||
struct device *dev;
|
||||
struct clk *emu_clk;
|
||||
struct mutex mutex;
|
||||
};
|
||||
|
||||
static struct tracectx tracer;
|
||||
static struct tracectx tracer = {
|
||||
.range_start = (unsigned long)_stext,
|
||||
.range_end = (unsigned long)_etext,
|
||||
};
|
||||
|
||||
static inline bool trace_isrunning(struct tracectx *t)
|
||||
{
|
||||
return !!(t->flags & TRACER_RUNNING);
|
||||
}
|
||||
|
||||
static int etm_setup_address_range(struct tracectx *t, int n,
|
||||
static int etm_setup_address_range(struct tracectx *t, int id, int n,
|
||||
unsigned long start, unsigned long end, int exclude, int data)
|
||||
{
|
||||
u32 flags = ETMAAT_ARM | ETMAAT_IGNCONTEXTID | ETMAAT_NSONLY | \
|
||||
u32 flags = ETMAAT_ARM | ETMAAT_IGNCONTEXTID | ETMAAT_IGNSECURITY |
|
||||
ETMAAT_NOVALCMP;
|
||||
|
||||
if (n < 1 || n > t->ncmppairs)
|
||||
|
|
@ -72,95 +84,185 @@ static int etm_setup_address_range(struct tracectx *t, int n,
|
|||
flags |= ETMAAT_IEXEC;
|
||||
|
||||
/* first comparator for the range */
|
||||
etm_writel(t, flags, ETMR_COMP_ACC_TYPE(n * 2));
|
||||
etm_writel(t, start, ETMR_COMP_VAL(n * 2));
|
||||
etm_writel(t, id, flags, ETMR_COMP_ACC_TYPE(n * 2));
|
||||
etm_writel(t, id, start, ETMR_COMP_VAL(n * 2));
|
||||
|
||||
/* second comparator is right next to it */
|
||||
etm_writel(t, flags, ETMR_COMP_ACC_TYPE(n * 2 + 1));
|
||||
etm_writel(t, end, ETMR_COMP_VAL(n * 2 + 1));
|
||||
etm_writel(t, id, flags, ETMR_COMP_ACC_TYPE(n * 2 + 1));
|
||||
etm_writel(t, id, end, ETMR_COMP_VAL(n * 2 + 1));
|
||||
|
||||
flags = exclude ? ETMTE_INCLEXCL : 0;
|
||||
etm_writel(t, flags | (1 << n), ETMR_TRACEENCTRL);
|
||||
if (data) {
|
||||
flags = exclude ? ETMVDC3_EXCLONLY : 0;
|
||||
if (exclude)
|
||||
n += 8;
|
||||
etm_writel(t, id, flags | BIT(n), ETMR_VIEWDATACTRL3);
|
||||
} else {
|
||||
flags = exclude ? ETMTE_INCLEXCL : 0;
|
||||
etm_writel(t, id, flags | (1 << n), ETMR_TRACEENCTRL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int trace_start_etm(struct tracectx *t, int id)
|
||||
{
|
||||
u32 v;
|
||||
unsigned long timeout = TRACER_TIMEOUT;
|
||||
|
||||
v = ETMCTRL_OPTS | ETMCTRL_PROGRAM | ETMCTRL_PORTSIZE(t->etm_portsz);
|
||||
v |= ETMCTRL_CONTEXTIDSIZE(t->etm_contextid_size);
|
||||
|
||||
if (t->flags & TRACER_CYCLE_ACC)
|
||||
v |= ETMCTRL_CYCLEACCURATE;
|
||||
|
||||
if (t->flags & TRACER_BRANCHOUTPUT)
|
||||
v |= ETMCTRL_BRANCH_OUTPUT;
|
||||
|
||||
if (t->flags & TRACER_TRACE_DATA)
|
||||
v |= ETMCTRL_DATA_DO_ADDR;
|
||||
|
||||
if (t->flags & TRACER_TIMESTAMP)
|
||||
v |= ETMCTRL_TIMESTAMP_EN;
|
||||
|
||||
if (t->flags & TRACER_RETURN_STACK)
|
||||
v |= ETMCTRL_RETURN_STACK_EN;
|
||||
|
||||
etm_unlock(t, id);
|
||||
|
||||
etm_writel(t, id, v, ETMR_CTRL);
|
||||
|
||||
while (!(etm_readl(t, id, ETMR_CTRL) & ETMCTRL_PROGRAM) && --timeout)
|
||||
;
|
||||
if (!timeout) {
|
||||
dev_dbg(t->dev, "Waiting for progbit to assert timed out\n");
|
||||
etm_lock(t, id);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (t->range_start || t->range_end)
|
||||
etm_setup_address_range(t, id, 1,
|
||||
t->range_start, t->range_end, 0, 0);
|
||||
else
|
||||
etm_writel(t, id, ETMTE_INCLEXCL, ETMR_TRACEENCTRL);
|
||||
|
||||
etm_writel(t, id, 0, ETMR_TRACEENCTRL2);
|
||||
etm_writel(t, id, 0, ETMR_TRACESSCTRL);
|
||||
etm_writel(t, id, 0x6f, ETMR_TRACEENEVT);
|
||||
|
||||
etm_writel(t, id, 0, ETMR_VIEWDATACTRL1);
|
||||
etm_writel(t, id, 0, ETMR_VIEWDATACTRL2);
|
||||
|
||||
if (t->data_range_start || t->data_range_end)
|
||||
etm_setup_address_range(t, id, 2, t->data_range_start,
|
||||
t->data_range_end, 0, 1);
|
||||
else
|
||||
etm_writel(t, id, ETMVDC3_EXCLONLY, ETMR_VIEWDATACTRL3);
|
||||
|
||||
etm_writel(t, id, 0x6f, ETMR_VIEWDATAEVT);
|
||||
|
||||
v &= ~ETMCTRL_PROGRAM;
|
||||
v |= ETMCTRL_PORTSEL;
|
||||
|
||||
etm_writel(t, id, v, ETMR_CTRL);
|
||||
|
||||
timeout = TRACER_TIMEOUT;
|
||||
while (etm_readl(t, id, ETMR_CTRL) & ETMCTRL_PROGRAM && --timeout)
|
||||
;
|
||||
if (!timeout) {
|
||||
dev_dbg(t->dev, "Waiting for progbit to deassert timed out\n");
|
||||
etm_lock(t, id);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
etm_lock(t, id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int trace_start(struct tracectx *t)
|
||||
{
|
||||
u32 v;
|
||||
unsigned long timeout = TRACER_TIMEOUT;
|
||||
int ret;
|
||||
int id;
|
||||
u32 etb_fc = t->etb_fc;
|
||||
|
||||
etb_unlock(t);
|
||||
|
||||
etb_writel(t, 0, ETBR_FORMATTERCTRL);
|
||||
t->dump_initial_etb = false;
|
||||
etb_writel(t, 0, ETBR_WRITEADDR);
|
||||
etb_writel(t, etb_fc, ETBR_FORMATTERCTRL);
|
||||
etb_writel(t, 1, ETBR_CTRL);
|
||||
|
||||
etb_lock(t);
|
||||
|
||||
/* configure etm */
|
||||
v = ETMCTRL_OPTS | ETMCTRL_PROGRAM | ETMCTRL_PORTSIZE(t->etm_portsz);
|
||||
|
||||
if (t->flags & TRACER_CYCLE_ACC)
|
||||
v |= ETMCTRL_CYCLEACCURATE;
|
||||
|
||||
etm_unlock(t);
|
||||
|
||||
etm_writel(t, v, ETMR_CTRL);
|
||||
|
||||
while (!(etm_readl(t, ETMR_CTRL) & ETMCTRL_PROGRAM) && --timeout)
|
||||
;
|
||||
if (!timeout) {
|
||||
dev_dbg(t->dev, "Waiting for progbit to assert timed out\n");
|
||||
etm_lock(t);
|
||||
return -EFAULT;
|
||||
/* configure etm(s) */
|
||||
for (id = 0; id < t->etm_regs_count; id++) {
|
||||
ret = trace_start_etm(t, id);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
etm_setup_address_range(t, 1, (unsigned long)_stext,
|
||||
(unsigned long)_etext, 0, 0);
|
||||
etm_writel(t, 0, ETMR_TRACEENCTRL2);
|
||||
etm_writel(t, 0, ETMR_TRACESSCTRL);
|
||||
etm_writel(t, 0x6f, ETMR_TRACEENEVT);
|
||||
|
||||
v &= ~ETMCTRL_PROGRAM;
|
||||
v |= ETMCTRL_PORTSEL;
|
||||
|
||||
etm_writel(t, v, ETMR_CTRL);
|
||||
|
||||
timeout = TRACER_TIMEOUT;
|
||||
while (etm_readl(t, ETMR_CTRL) & ETMCTRL_PROGRAM && --timeout)
|
||||
;
|
||||
if (!timeout) {
|
||||
dev_dbg(t->dev, "Waiting for progbit to deassert timed out\n");
|
||||
etm_lock(t);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
etm_lock(t);
|
||||
|
||||
t->flags |= TRACER_RUNNING;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int trace_stop(struct tracectx *t)
|
||||
static int trace_stop_etm(struct tracectx *t, int id)
|
||||
{
|
||||
unsigned long timeout = TRACER_TIMEOUT;
|
||||
|
||||
etm_unlock(t);
|
||||
etm_unlock(t, id);
|
||||
|
||||
etm_writel(t, 0x440, ETMR_CTRL);
|
||||
while (!(etm_readl(t, ETMR_CTRL) & ETMCTRL_PROGRAM) && --timeout)
|
||||
etm_writel(t, id, 0x440, ETMR_CTRL);
|
||||
while (!(etm_readl(t, id, ETMR_CTRL) & ETMCTRL_PROGRAM) && --timeout)
|
||||
;
|
||||
if (!timeout) {
|
||||
dev_dbg(t->dev, "Waiting for progbit to assert timed out\n");
|
||||
etm_lock(t);
|
||||
dev_err(t->dev,
|
||||
"etm%d: Waiting for progbit to assert timed out\n",
|
||||
id);
|
||||
etm_lock(t, id);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
etm_lock(t);
|
||||
etm_lock(t, id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int trace_power_down_etm(struct tracectx *t, int id)
|
||||
{
|
||||
unsigned long timeout = TRACER_TIMEOUT;
|
||||
etm_unlock(t, id);
|
||||
while (!(etm_readl(t, id, ETMR_STATUS) & ETMST_PROGBIT) && --timeout)
|
||||
;
|
||||
if (!timeout) {
|
||||
dev_err(t->dev, "etm%d: Waiting for status progbit to assert timed out\n",
|
||||
id);
|
||||
etm_lock(t, id);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
etm_writel(t, id, 0x441, ETMR_CTRL);
|
||||
|
||||
etm_lock(t, id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int trace_stop(struct tracectx *t)
|
||||
{
|
||||
int id;
|
||||
unsigned long timeout = TRACER_TIMEOUT;
|
||||
u32 etb_fc = t->etb_fc;
|
||||
|
||||
for (id = 0; id < t->etm_regs_count; id++)
|
||||
trace_stop_etm(t, id);
|
||||
|
||||
for (id = 0; id < t->etm_regs_count; id++)
|
||||
trace_power_down_etm(t, id);
|
||||
|
||||
etb_unlock(t);
|
||||
etb_writel(t, ETBFF_MANUAL_FLUSH, ETBR_FORMATTERCTRL);
|
||||
if (etb_fc) {
|
||||
etb_fc |= ETBFF_STOPFL;
|
||||
etb_writel(t, t->etb_fc, ETBR_FORMATTERCTRL);
|
||||
}
|
||||
etb_writel(t, etb_fc | ETBFF_MANUAL_FLUSH, ETBR_FORMATTERCTRL);
|
||||
|
||||
timeout = TRACER_TIMEOUT;
|
||||
while (etb_readl(t, ETBR_FORMATTERCTRL) &
|
||||
|
|
@ -185,24 +287,15 @@ static int trace_stop(struct tracectx *t)
|
|||
static int etb_getdatalen(struct tracectx *t)
|
||||
{
|
||||
u32 v;
|
||||
int rp, wp;
|
||||
int wp;
|
||||
|
||||
v = etb_readl(t, ETBR_STATUS);
|
||||
|
||||
if (v & 1)
|
||||
return t->etb_bufsz;
|
||||
|
||||
rp = etb_readl(t, ETBR_READADDR);
|
||||
wp = etb_readl(t, ETBR_WRITEADDR);
|
||||
|
||||
if (rp > wp) {
|
||||
etb_writel(t, 0, ETBR_READADDR);
|
||||
etb_writel(t, 0, ETBR_WRITEADDR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return wp - rp;
|
||||
return wp;
|
||||
}
|
||||
|
||||
/* sysrq+v will always stop the running trace and leave it at that */
|
||||
|
|
@ -235,21 +328,18 @@ static void etm_dump(void)
|
|||
printk("%08x", cpu_to_be32(etb_readl(t, ETBR_READMEM)));
|
||||
printk(KERN_INFO "\n--- ETB buffer end ---\n");
|
||||
|
||||
/* deassert the overflow bit */
|
||||
etb_writel(t, 1, ETBR_CTRL);
|
||||
etb_writel(t, 0, ETBR_CTRL);
|
||||
|
||||
etb_writel(t, 0, ETBR_TRIGGERCOUNT);
|
||||
etb_writel(t, 0, ETBR_READADDR);
|
||||
etb_writel(t, 0, ETBR_WRITEADDR);
|
||||
|
||||
etb_lock(t);
|
||||
}
|
||||
|
||||
static void sysrq_etm_dump(int key)
|
||||
{
|
||||
if (!mutex_trylock(&tracer.mutex)) {
|
||||
printk(KERN_INFO "Tracing hardware busy\n");
|
||||
return;
|
||||
}
|
||||
dev_dbg(tracer.dev, "Dumping ETB buffer\n");
|
||||
etm_dump();
|
||||
mutex_unlock(&tracer.mutex);
|
||||
}
|
||||
|
||||
static struct sysrq_key_op sysrq_etm_op = {
|
||||
|
|
@ -276,6 +366,10 @@ static ssize_t etb_read(struct file *file, char __user *data,
|
|||
struct tracectx *t = file->private_data;
|
||||
u32 first = 0;
|
||||
u32 *buf;
|
||||
int wpos;
|
||||
int skip;
|
||||
long wlength;
|
||||
loff_t pos = *ppos;
|
||||
|
||||
mutex_lock(&t->mutex);
|
||||
|
||||
|
|
@ -287,31 +381,39 @@ static ssize_t etb_read(struct file *file, char __user *data,
|
|||
etb_unlock(t);
|
||||
|
||||
total = etb_getdatalen(t);
|
||||
if (total == 0 && t->dump_initial_etb)
|
||||
total = t->etb_bufsz;
|
||||
if (total == t->etb_bufsz)
|
||||
first = etb_readl(t, ETBR_WRITEADDR);
|
||||
|
||||
if (pos > total * 4) {
|
||||
skip = 0;
|
||||
wpos = total;
|
||||
} else {
|
||||
skip = (int)pos % 4;
|
||||
wpos = (int)pos / 4;
|
||||
}
|
||||
total -= wpos;
|
||||
first = (first + wpos) % t->etb_bufsz;
|
||||
|
||||
etb_writel(t, first, ETBR_READADDR);
|
||||
|
||||
length = min(total * 4, (int)len);
|
||||
buf = vmalloc(length);
|
||||
wlength = min(total, DIV_ROUND_UP(skip + (int)len, 4));
|
||||
length = min(total * 4 - skip, (int)len);
|
||||
buf = vmalloc(wlength * 4);
|
||||
|
||||
dev_dbg(t->dev, "ETB buffer length: %d\n", total);
|
||||
dev_dbg(t->dev, "ETB read %ld bytes to %lld from %ld words at %d\n",
|
||||
length, pos, wlength, first);
|
||||
dev_dbg(t->dev, "ETB buffer length: %d\n", total + wpos);
|
||||
dev_dbg(t->dev, "ETB status reg: %x\n", etb_readl(t, ETBR_STATUS));
|
||||
for (i = 0; i < length / 4; i++)
|
||||
for (i = 0; i < wlength; i++)
|
||||
buf[i] = etb_readl(t, ETBR_READMEM);
|
||||
|
||||
/* the only way to deassert overflow bit in ETB status is this */
|
||||
etb_writel(t, 1, ETBR_CTRL);
|
||||
etb_writel(t, 0, ETBR_CTRL);
|
||||
|
||||
etb_writel(t, 0, ETBR_WRITEADDR);
|
||||
etb_writel(t, 0, ETBR_READADDR);
|
||||
etb_writel(t, 0, ETBR_TRIGGERCOUNT);
|
||||
|
||||
etb_lock(t);
|
||||
|
||||
length -= copy_to_user(data, buf, length);
|
||||
length -= copy_to_user(data, (u8 *)buf + skip, length);
|
||||
vfree(buf);
|
||||
*ppos = pos + length;
|
||||
|
||||
out:
|
||||
mutex_unlock(&t->mutex);
|
||||
|
|
@ -348,28 +450,17 @@ static int etb_probe(struct amba_device *dev, const struct amba_id *id)
|
|||
if (ret)
|
||||
goto out;
|
||||
|
||||
mutex_lock(&t->mutex);
|
||||
t->etb_regs = ioremap_nocache(dev->res.start, resource_size(&dev->res));
|
||||
if (!t->etb_regs) {
|
||||
ret = -ENOMEM;
|
||||
goto out_release;
|
||||
}
|
||||
|
||||
t->dev = &dev->dev;
|
||||
t->dump_initial_etb = true;
|
||||
amba_set_drvdata(dev, t);
|
||||
|
||||
etb_miscdev.parent = &dev->dev;
|
||||
|
||||
ret = misc_register(&etb_miscdev);
|
||||
if (ret)
|
||||
goto out_unmap;
|
||||
|
||||
t->emu_clk = clk_get(&dev->dev, "emu_src_ck");
|
||||
if (IS_ERR(t->emu_clk)) {
|
||||
dev_dbg(&dev->dev, "Failed to obtain emu_src_ck.\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
clk_enable(t->emu_clk);
|
||||
|
||||
etb_unlock(t);
|
||||
t->etb_bufsz = etb_readl(t, ETBR_DEPTH);
|
||||
dev_dbg(&dev->dev, "Size: %x\n", t->etb_bufsz);
|
||||
|
|
@ -378,6 +469,20 @@ static int etb_probe(struct amba_device *dev, const struct amba_id *id)
|
|||
etb_writel(t, 0, ETBR_CTRL);
|
||||
etb_writel(t, 0x1000, ETBR_FORMATTERCTRL);
|
||||
etb_lock(t);
|
||||
mutex_unlock(&t->mutex);
|
||||
|
||||
etb_miscdev.parent = &dev->dev;
|
||||
|
||||
ret = misc_register(&etb_miscdev);
|
||||
if (ret)
|
||||
goto out_unmap;
|
||||
|
||||
/* Get optional clock. Currently used to select clock source on omap3 */
|
||||
t->emu_clk = clk_get(&dev->dev, "emu_src_ck");
|
||||
if (IS_ERR(t->emu_clk))
|
||||
dev_dbg(&dev->dev, "Failed to obtain emu_src_ck.\n");
|
||||
else
|
||||
clk_enable(t->emu_clk);
|
||||
|
||||
dev_dbg(&dev->dev, "ETB AMBA driver initialized.\n");
|
||||
|
||||
|
|
@ -385,10 +490,13 @@ static int etb_probe(struct amba_device *dev, const struct amba_id *id)
|
|||
return ret;
|
||||
|
||||
out_unmap:
|
||||
mutex_lock(&t->mutex);
|
||||
amba_set_drvdata(dev, NULL);
|
||||
iounmap(t->etb_regs);
|
||||
t->etb_regs = NULL;
|
||||
|
||||
out_release:
|
||||
mutex_unlock(&t->mutex);
|
||||
amba_release_regions(dev);
|
||||
|
||||
return ret;
|
||||
|
|
@ -403,8 +511,10 @@ static int etb_remove(struct amba_device *dev)
|
|||
iounmap(t->etb_regs);
|
||||
t->etb_regs = NULL;
|
||||
|
||||
clk_disable(t->emu_clk);
|
||||
clk_put(t->emu_clk);
|
||||
if (!IS_ERR(t->emu_clk)) {
|
||||
clk_disable(t->emu_clk);
|
||||
clk_put(t->emu_clk);
|
||||
}
|
||||
|
||||
amba_release_regions(dev);
|
||||
|
||||
|
|
@ -448,7 +558,10 @@ static ssize_t trace_running_store(struct kobject *kobj,
|
|||
return -EINVAL;
|
||||
|
||||
mutex_lock(&tracer.mutex);
|
||||
ret = value ? trace_start(&tracer) : trace_stop(&tracer);
|
||||
if (!tracer.etb_regs)
|
||||
ret = -ENODEV;
|
||||
else
|
||||
ret = value ? trace_start(&tracer) : trace_stop(&tracer);
|
||||
mutex_unlock(&tracer.mutex);
|
||||
|
||||
return ret ? : n;
|
||||
|
|
@ -463,36 +576,50 @@ static ssize_t trace_info_show(struct kobject *kobj,
|
|||
{
|
||||
u32 etb_wa, etb_ra, etb_st, etb_fc, etm_ctrl, etm_st;
|
||||
int datalen;
|
||||
int id;
|
||||
int ret;
|
||||
|
||||
etb_unlock(&tracer);
|
||||
datalen = etb_getdatalen(&tracer);
|
||||
etb_wa = etb_readl(&tracer, ETBR_WRITEADDR);
|
||||
etb_ra = etb_readl(&tracer, ETBR_READADDR);
|
||||
etb_st = etb_readl(&tracer, ETBR_STATUS);
|
||||
etb_fc = etb_readl(&tracer, ETBR_FORMATTERCTRL);
|
||||
etb_lock(&tracer);
|
||||
mutex_lock(&tracer.mutex);
|
||||
if (tracer.etb_regs) {
|
||||
etb_unlock(&tracer);
|
||||
datalen = etb_getdatalen(&tracer);
|
||||
etb_wa = etb_readl(&tracer, ETBR_WRITEADDR);
|
||||
etb_ra = etb_readl(&tracer, ETBR_READADDR);
|
||||
etb_st = etb_readl(&tracer, ETBR_STATUS);
|
||||
etb_fc = etb_readl(&tracer, ETBR_FORMATTERCTRL);
|
||||
etb_lock(&tracer);
|
||||
} else {
|
||||
etb_wa = etb_ra = etb_st = etb_fc = ~0;
|
||||
datalen = -1;
|
||||
}
|
||||
|
||||
etm_unlock(&tracer);
|
||||
etm_ctrl = etm_readl(&tracer, ETMR_CTRL);
|
||||
etm_st = etm_readl(&tracer, ETMR_STATUS);
|
||||
etm_lock(&tracer);
|
||||
|
||||
return sprintf(buf, "Trace buffer len: %d\nComparator pairs: %d\n"
|
||||
ret = sprintf(buf, "Trace buffer len: %d\nComparator pairs: %d\n"
|
||||
"ETBR_WRITEADDR:\t%08x\n"
|
||||
"ETBR_READADDR:\t%08x\n"
|
||||
"ETBR_STATUS:\t%08x\n"
|
||||
"ETBR_FORMATTERCTRL:\t%08x\n"
|
||||
"ETMR_CTRL:\t%08x\n"
|
||||
"ETMR_STATUS:\t%08x\n",
|
||||
"ETBR_FORMATTERCTRL:\t%08x\n",
|
||||
datalen,
|
||||
tracer.ncmppairs,
|
||||
etb_wa,
|
||||
etb_ra,
|
||||
etb_st,
|
||||
etb_fc,
|
||||
etb_fc
|
||||
);
|
||||
|
||||
for (id = 0; id < tracer.etm_regs_count; id++) {
|
||||
etm_unlock(&tracer, id);
|
||||
etm_ctrl = etm_readl(&tracer, id, ETMR_CTRL);
|
||||
etm_st = etm_readl(&tracer, id, ETMR_STATUS);
|
||||
etm_lock(&tracer, id);
|
||||
ret += sprintf(buf + ret, "ETMR_CTRL:\t%08x\n"
|
||||
"ETMR_STATUS:\t%08x\n",
|
||||
etm_ctrl,
|
||||
etm_st
|
||||
);
|
||||
}
|
||||
mutex_unlock(&tracer.mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct kobj_attribute trace_info_attr =
|
||||
|
|
@ -531,42 +658,260 @@ static ssize_t trace_mode_store(struct kobject *kobj,
|
|||
static struct kobj_attribute trace_mode_attr =
|
||||
__ATTR(trace_mode, 0644, trace_mode_show, trace_mode_store);
|
||||
|
||||
static ssize_t trace_contextid_size_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
/* 0: No context id tracing, 1: One byte, 2: Two bytes, 3: Four bytes */
|
||||
return sprintf(buf, "%d\n", (1 << tracer.etm_contextid_size) >> 1);
|
||||
}
|
||||
|
||||
static ssize_t trace_contextid_size_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t n)
|
||||
{
|
||||
unsigned int contextid_size;
|
||||
|
||||
if (sscanf(buf, "%u", &contextid_size) != 1)
|
||||
return -EINVAL;
|
||||
|
||||
if (contextid_size == 3 || contextid_size > 4)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&tracer.mutex);
|
||||
tracer.etm_contextid_size = fls(contextid_size);
|
||||
mutex_unlock(&tracer.mutex);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static struct kobj_attribute trace_contextid_size_attr =
|
||||
__ATTR(trace_contextid_size, 0644,
|
||||
trace_contextid_size_show, trace_contextid_size_store);
|
||||
|
||||
static ssize_t trace_branch_output_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n", !!(tracer.flags & TRACER_BRANCHOUTPUT));
|
||||
}
|
||||
|
||||
static ssize_t trace_branch_output_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t n)
|
||||
{
|
||||
unsigned int branch_output;
|
||||
|
||||
if (sscanf(buf, "%u", &branch_output) != 1)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&tracer.mutex);
|
||||
if (branch_output) {
|
||||
tracer.flags |= TRACER_BRANCHOUTPUT;
|
||||
/* Branch broadcasting is incompatible with the return stack */
|
||||
tracer.flags &= ~TRACER_RETURN_STACK;
|
||||
} else {
|
||||
tracer.flags &= ~TRACER_BRANCHOUTPUT;
|
||||
}
|
||||
mutex_unlock(&tracer.mutex);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static struct kobj_attribute trace_branch_output_attr =
|
||||
__ATTR(trace_branch_output, 0644,
|
||||
trace_branch_output_show, trace_branch_output_store);
|
||||
|
||||
static ssize_t trace_return_stack_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n", !!(tracer.flags & TRACER_RETURN_STACK));
|
||||
}
|
||||
|
||||
static ssize_t trace_return_stack_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t n)
|
||||
{
|
||||
unsigned int return_stack;
|
||||
|
||||
if (sscanf(buf, "%u", &return_stack) != 1)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&tracer.mutex);
|
||||
if (return_stack) {
|
||||
tracer.flags |= TRACER_RETURN_STACK;
|
||||
/* Return stack is incompatible with branch broadcasting */
|
||||
tracer.flags &= ~TRACER_BRANCHOUTPUT;
|
||||
} else {
|
||||
tracer.flags &= ~TRACER_RETURN_STACK;
|
||||
}
|
||||
mutex_unlock(&tracer.mutex);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static struct kobj_attribute trace_return_stack_attr =
|
||||
__ATTR(trace_return_stack, 0644,
|
||||
trace_return_stack_show, trace_return_stack_store);
|
||||
|
||||
static ssize_t trace_timestamp_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n", !!(tracer.flags & TRACER_TIMESTAMP));
|
||||
}
|
||||
|
||||
static ssize_t trace_timestamp_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t n)
|
||||
{
|
||||
unsigned int timestamp;
|
||||
|
||||
if (sscanf(buf, "%u", ×tamp) != 1)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&tracer.mutex);
|
||||
if (timestamp)
|
||||
tracer.flags |= TRACER_TIMESTAMP;
|
||||
else
|
||||
tracer.flags &= ~TRACER_TIMESTAMP;
|
||||
mutex_unlock(&tracer.mutex);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static struct kobj_attribute trace_timestamp_attr =
|
||||
__ATTR(trace_timestamp, 0644,
|
||||
trace_timestamp_show, trace_timestamp_store);
|
||||
|
||||
static ssize_t trace_range_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%08lx %08lx\n",
|
||||
tracer.range_start, tracer.range_end);
|
||||
}
|
||||
|
||||
static ssize_t trace_range_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t n)
|
||||
{
|
||||
unsigned long range_start, range_end;
|
||||
|
||||
if (sscanf(buf, "%lx %lx", &range_start, &range_end) != 2)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&tracer.mutex);
|
||||
tracer.range_start = range_start;
|
||||
tracer.range_end = range_end;
|
||||
mutex_unlock(&tracer.mutex);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
static struct kobj_attribute trace_range_attr =
|
||||
__ATTR(trace_range, 0644, trace_range_show, trace_range_store);
|
||||
|
||||
static ssize_t trace_data_range_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
unsigned long range_start;
|
||||
u64 range_end;
|
||||
mutex_lock(&tracer.mutex);
|
||||
range_start = tracer.data_range_start;
|
||||
range_end = tracer.data_range_end;
|
||||
if (!range_end && (tracer.flags & TRACER_TRACE_DATA))
|
||||
range_end = 0x100000000ULL;
|
||||
mutex_unlock(&tracer.mutex);
|
||||
return sprintf(buf, "%08lx %08llx\n", range_start, range_end);
|
||||
}
|
||||
|
||||
static ssize_t trace_data_range_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t n)
|
||||
{
|
||||
unsigned long range_start;
|
||||
u64 range_end;
|
||||
|
||||
if (sscanf(buf, "%lx %llx", &range_start, &range_end) != 2)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&tracer.mutex);
|
||||
tracer.data_range_start = range_start;
|
||||
tracer.data_range_end = (unsigned long)range_end;
|
||||
if (range_end)
|
||||
tracer.flags |= TRACER_TRACE_DATA;
|
||||
else
|
||||
tracer.flags &= ~TRACER_TRACE_DATA;
|
||||
mutex_unlock(&tracer.mutex);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
static struct kobj_attribute trace_data_range_attr =
|
||||
__ATTR(trace_data_range, 0644,
|
||||
trace_data_range_show, trace_data_range_store);
|
||||
|
||||
static int etm_probe(struct amba_device *dev, const struct amba_id *id)
|
||||
{
|
||||
struct tracectx *t = &tracer;
|
||||
int ret = 0;
|
||||
void __iomem **new_regs;
|
||||
int new_count;
|
||||
u32 etmccr;
|
||||
u32 etmidr;
|
||||
u32 etmccer = 0;
|
||||
u8 etm_version = 0;
|
||||
|
||||
if (t->etm_regs) {
|
||||
dev_dbg(&dev->dev, "ETM already initialized\n");
|
||||
ret = -EBUSY;
|
||||
mutex_lock(&t->mutex);
|
||||
new_count = t->etm_regs_count + 1;
|
||||
new_regs = krealloc(t->etm_regs,
|
||||
sizeof(t->etm_regs[0]) * new_count, GFP_KERNEL);
|
||||
|
||||
if (!new_regs) {
|
||||
dev_dbg(&dev->dev, "Failed to allocate ETM register array\n");
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
t->etm_regs = new_regs;
|
||||
|
||||
ret = amba_request_regions(dev, NULL);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
t->etm_regs = ioremap_nocache(dev->res.start, resource_size(&dev->res));
|
||||
if (!t->etm_regs) {
|
||||
t->etm_regs[t->etm_regs_count] =
|
||||
ioremap_nocache(dev->res.start, resource_size(&dev->res));
|
||||
if (!t->etm_regs[t->etm_regs_count]) {
|
||||
ret = -ENOMEM;
|
||||
goto out_release;
|
||||
}
|
||||
|
||||
amba_set_drvdata(dev, t);
|
||||
amba_set_drvdata(dev, t->etm_regs[t->etm_regs_count]);
|
||||
|
||||
mutex_init(&t->mutex);
|
||||
t->dev = &dev->dev;
|
||||
t->flags = TRACER_CYCLE_ACC;
|
||||
t->flags = TRACER_CYCLE_ACC | TRACER_TRACE_DATA | TRACER_BRANCHOUTPUT;
|
||||
t->etm_portsz = 1;
|
||||
t->etm_contextid_size = 3;
|
||||
|
||||
etm_unlock(t);
|
||||
(void)etm_readl(t, ETMMR_PDSR);
|
||||
etm_unlock(t, t->etm_regs_count);
|
||||
(void)etm_readl(t, t->etm_regs_count, ETMMR_PDSR);
|
||||
/* dummy first read */
|
||||
(void)etm_readl(&tracer, ETMMR_OSSRR);
|
||||
(void)etm_readl(&tracer, t->etm_regs_count, ETMMR_OSSRR);
|
||||
|
||||
t->ncmppairs = etm_readl(t, ETMR_CONFCODE) & 0xf;
|
||||
etm_writel(t, 0x440, ETMR_CTRL);
|
||||
etm_lock(t);
|
||||
etmccr = etm_readl(t, t->etm_regs_count, ETMR_CONFCODE);
|
||||
t->ncmppairs = etmccr & 0xf;
|
||||
if (etmccr & ETMCCR_ETMIDR_PRESENT) {
|
||||
etmidr = etm_readl(t, t->etm_regs_count, ETMR_ID);
|
||||
etm_version = ETMIDR_VERSION(etmidr);
|
||||
if (etm_version >= ETMIDR_VERSION_3_1)
|
||||
etmccer = etm_readl(t, t->etm_regs_count, ETMR_CCE);
|
||||
}
|
||||
etm_writel(t, t->etm_regs_count, 0x441, ETMR_CTRL);
|
||||
etm_writel(t, t->etm_regs_count, new_count, ETMR_TRACEIDR);
|
||||
etm_lock(t, t->etm_regs_count);
|
||||
|
||||
ret = sysfs_create_file(&dev->dev.kobj,
|
||||
&trace_running_attr.attr);
|
||||
|
|
@ -582,35 +927,100 @@ static int etm_probe(struct amba_device *dev, const struct amba_id *id)
|
|||
if (ret)
|
||||
dev_dbg(&dev->dev, "Failed to create trace_mode in sysfs\n");
|
||||
|
||||
dev_dbg(t->dev, "ETM AMBA driver initialized.\n");
|
||||
ret = sysfs_create_file(&dev->dev.kobj,
|
||||
&trace_contextid_size_attr.attr);
|
||||
if (ret)
|
||||
dev_dbg(&dev->dev,
|
||||
"Failed to create trace_contextid_size in sysfs\n");
|
||||
|
||||
ret = sysfs_create_file(&dev->dev.kobj,
|
||||
&trace_branch_output_attr.attr);
|
||||
if (ret)
|
||||
dev_dbg(&dev->dev,
|
||||
"Failed to create trace_branch_output in sysfs\n");
|
||||
|
||||
if (etmccer & ETMCCER_RETURN_STACK_IMPLEMENTED) {
|
||||
ret = sysfs_create_file(&dev->dev.kobj,
|
||||
&trace_return_stack_attr.attr);
|
||||
if (ret)
|
||||
dev_dbg(&dev->dev,
|
||||
"Failed to create trace_return_stack in sysfs\n");
|
||||
}
|
||||
|
||||
if (etmccer & ETMCCER_TIMESTAMPING_IMPLEMENTED) {
|
||||
ret = sysfs_create_file(&dev->dev.kobj,
|
||||
&trace_timestamp_attr.attr);
|
||||
if (ret)
|
||||
dev_dbg(&dev->dev,
|
||||
"Failed to create trace_timestamp in sysfs\n");
|
||||
}
|
||||
|
||||
ret = sysfs_create_file(&dev->dev.kobj, &trace_range_attr.attr);
|
||||
if (ret)
|
||||
dev_dbg(&dev->dev, "Failed to create trace_range in sysfs\n");
|
||||
|
||||
if (etm_version < ETMIDR_VERSION_PFT_1_0) {
|
||||
ret = sysfs_create_file(&dev->dev.kobj,
|
||||
&trace_data_range_attr.attr);
|
||||
if (ret)
|
||||
dev_dbg(&dev->dev,
|
||||
"Failed to create trace_data_range in sysfs\n");
|
||||
} else {
|
||||
tracer.flags &= ~TRACER_TRACE_DATA;
|
||||
}
|
||||
|
||||
dev_dbg(&dev->dev, "ETM AMBA driver initialized.\n");
|
||||
|
||||
/* Enable formatter if there are multiple trace sources */
|
||||
if (new_count > 1)
|
||||
t->etb_fc = ETBFF_ENFCONT | ETBFF_ENFTC;
|
||||
|
||||
t->etm_regs_count = new_count;
|
||||
|
||||
out:
|
||||
mutex_unlock(&t->mutex);
|
||||
return ret;
|
||||
|
||||
out_unmap:
|
||||
amba_set_drvdata(dev, NULL);
|
||||
iounmap(t->etm_regs);
|
||||
iounmap(t->etm_regs[t->etm_regs_count]);
|
||||
|
||||
out_release:
|
||||
amba_release_regions(dev);
|
||||
|
||||
mutex_unlock(&t->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int etm_remove(struct amba_device *dev)
|
||||
{
|
||||
struct tracectx *t = amba_get_drvdata(dev);
|
||||
|
||||
amba_set_drvdata(dev, NULL);
|
||||
|
||||
iounmap(t->etm_regs);
|
||||
t->etm_regs = NULL;
|
||||
|
||||
amba_release_regions(dev);
|
||||
int i;
|
||||
struct tracectx *t = &tracer;
|
||||
void __iomem *etm_regs = amba_get_drvdata(dev);
|
||||
|
||||
sysfs_remove_file(&dev->dev.kobj, &trace_running_attr.attr);
|
||||
sysfs_remove_file(&dev->dev.kobj, &trace_info_attr.attr);
|
||||
sysfs_remove_file(&dev->dev.kobj, &trace_mode_attr.attr);
|
||||
sysfs_remove_file(&dev->dev.kobj, &trace_range_attr.attr);
|
||||
sysfs_remove_file(&dev->dev.kobj, &trace_data_range_attr.attr);
|
||||
|
||||
amba_set_drvdata(dev, NULL);
|
||||
|
||||
mutex_lock(&t->mutex);
|
||||
for (i = 0; i < t->etm_regs_count; i++)
|
||||
if (t->etm_regs[i] == etm_regs)
|
||||
break;
|
||||
for (; i < t->etm_regs_count - 1; i++)
|
||||
t->etm_regs[i] = t->etm_regs[i + 1];
|
||||
t->etm_regs_count--;
|
||||
if (!t->etm_regs_count) {
|
||||
kfree(t->etm_regs);
|
||||
t->etm_regs = NULL;
|
||||
}
|
||||
mutex_unlock(&t->mutex);
|
||||
|
||||
iounmap(etm_regs);
|
||||
amba_release_regions(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -620,6 +1030,10 @@ static struct amba_id etm_ids[] = {
|
|||
.id = 0x0003b921,
|
||||
.mask = 0x0007ffff,
|
||||
},
|
||||
{
|
||||
.id = 0x0003b950,
|
||||
.mask = 0x0007ffff,
|
||||
},
|
||||
{ 0, 0 },
|
||||
};
|
||||
|
||||
|
|
@ -637,6 +1051,8 @@ static int __init etm_init(void)
|
|||
{
|
||||
int retval;
|
||||
|
||||
mutex_init(&tracer.mutex);
|
||||
|
||||
retval = amba_driver_register(&etb_driver);
|
||||
if (retval) {
|
||||
printk(KERN_ERR "Failed to register etb\n");
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
*/
|
||||
|
||||
#include <linux/ftrace.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#include <asm/cacheflush.h>
|
||||
|
|
@ -63,6 +64,20 @@ static unsigned long adjust_address(struct dyn_ftrace *rec, unsigned long addr)
|
|||
}
|
||||
#endif
|
||||
|
||||
int ftrace_arch_code_modify_prepare(void)
|
||||
{
|
||||
set_kernel_text_rw();
|
||||
set_all_modules_text_rw();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ftrace_arch_code_modify_post_process(void)
|
||||
{
|
||||
set_all_modules_text_ro();
|
||||
set_kernel_text_ro();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned long ftrace_call_replace(unsigned long pc, unsigned long addr)
|
||||
{
|
||||
return arm_gen_branch_link(pc, addr);
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
#include <linux/hw_breakpoint.h>
|
||||
#include <linux/cpuidle.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/console.h>
|
||||
|
||||
#include <asm/cacheflush.h>
|
||||
#include <asm/idmap.h>
|
||||
|
|
@ -57,9 +58,46 @@ static const char *isa_modes[] = {
|
|||
"ARM" , "Thumb" , "Jazelle", "ThumbEE"
|
||||
};
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
void arch_trigger_all_cpu_backtrace(void)
|
||||
{
|
||||
smp_send_all_cpu_backtrace();
|
||||
}
|
||||
#else
|
||||
void arch_trigger_all_cpu_backtrace(void)
|
||||
{
|
||||
dump_stack();
|
||||
}
|
||||
#endif
|
||||
|
||||
extern void call_with_stack(void (*fn)(void *), void *arg, void *sp);
|
||||
typedef void (*phys_reset_t)(unsigned long);
|
||||
|
||||
#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
|
||||
|
||||
/*
|
||||
* A temporary stack to use for CPU reset. This is static so that we
|
||||
* don't clobber it with the identity mapping. When running with this
|
||||
|
|
@ -147,6 +185,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();
|
||||
|
|
@ -156,6 +195,7 @@ void arch_cpu_idle_enter(void)
|
|||
void arch_cpu_idle_exit(void)
|
||||
{
|
||||
ledtrig_cpu(CPU_LED_IDLE_END);
|
||||
idle_notifier_call_chain(IDLE_END);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_HOTPLUG_CPU
|
||||
|
|
@ -195,6 +235,16 @@ __setup("reboot=", reboot_setup);
|
|||
*/
|
||||
void machine_shutdown(void)
|
||||
{
|
||||
#ifdef CONFIG_SMP
|
||||
/*
|
||||
* Disable preemption so we're guaranteed to
|
||||
* run to power off or reboot and prevent
|
||||
* the possibility of switching to another
|
||||
* thread that might wind up blocking on
|
||||
* one of the stopped CPUs.
|
||||
*/
|
||||
preempt_disable();
|
||||
#endif
|
||||
disable_nonboot_cpus();
|
||||
}
|
||||
|
||||
|
|
@ -240,6 +290,10 @@ void machine_restart(char *cmd)
|
|||
{
|
||||
smp_send_stop();
|
||||
|
||||
/* Flush the console to make sure all the relevant messages make it
|
||||
* out to the console drivers */
|
||||
arm_machine_flush_console();
|
||||
|
||||
arm_pm_restart(reboot_mode, cmd);
|
||||
|
||||
/* Give a grace period for failure to restart of 1s */
|
||||
|
|
@ -251,6 +305,77 @@ 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;
|
||||
|
||||
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)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
|
@ -307,6 +432,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)
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ enum ipi_msg_type {
|
|||
IPI_CALL_FUNC_SINGLE,
|
||||
IPI_CPU_STOP,
|
||||
IPI_COMPLETION,
|
||||
IPI_CPU_BACKTRACE,
|
||||
};
|
||||
|
||||
static DECLARE_COMPLETION(cpu_running);
|
||||
|
|
@ -465,6 +466,7 @@ static const char *ipi_types[NR_IPI] = {
|
|||
S(IPI_CALL_FUNC_SINGLE, "Single function call interrupts"),
|
||||
S(IPI_CPU_STOP, "CPU stop interrupts"),
|
||||
S(IPI_COMPLETION, "completion interrupts"),
|
||||
S(IPI_CPU_BACKTRACE, "CPU backtrace"),
|
||||
};
|
||||
|
||||
void show_ipi_list(struct seq_file *p, int prec)
|
||||
|
|
@ -603,6 +605,58 @@ static void ipi_complete(unsigned int cpu)
|
|||
complete(per_cpu(cpu_completion, cpu));
|
||||
}
|
||||
|
||||
static cpumask_t backtrace_mask;
|
||||
static DEFINE_RAW_SPINLOCK(backtrace_lock);
|
||||
|
||||
/* "in progress" flag of arch_trigger_all_cpu_backtrace */
|
||||
static unsigned long backtrace_flag;
|
||||
|
||||
void smp_send_all_cpu_backtrace(void)
|
||||
{
|
||||
unsigned int this_cpu = smp_processor_id();
|
||||
int i;
|
||||
|
||||
if (test_and_set_bit(0, &backtrace_flag))
|
||||
/*
|
||||
* If there is already a trigger_all_cpu_backtrace() in progress
|
||||
* (backtrace_flag == 1), don't output double cpu dump infos.
|
||||
*/
|
||||
return;
|
||||
|
||||
cpumask_copy(&backtrace_mask, cpu_online_mask);
|
||||
cpu_clear(this_cpu, backtrace_mask);
|
||||
|
||||
pr_info("Backtrace for cpu %d (current):\n", this_cpu);
|
||||
dump_stack();
|
||||
|
||||
pr_info("\nsending IPI to all other CPUs:\n");
|
||||
smp_cross_call(&backtrace_mask, IPI_CPU_BACKTRACE);
|
||||
|
||||
/* Wait for up to 10 seconds for all other CPUs to do the backtrace */
|
||||
for (i = 0; i < 10 * 1000; i++) {
|
||||
if (cpumask_empty(&backtrace_mask))
|
||||
break;
|
||||
mdelay(1);
|
||||
}
|
||||
|
||||
clear_bit(0, &backtrace_flag);
|
||||
smp_mb__after_clear_bit();
|
||||
}
|
||||
|
||||
/*
|
||||
* ipi_cpu_backtrace - handle IPI from smp_send_all_cpu_backtrace()
|
||||
*/
|
||||
static void ipi_cpu_backtrace(unsigned int cpu, struct pt_regs *regs)
|
||||
{
|
||||
if (cpu_isset(cpu, backtrace_mask)) {
|
||||
raw_spin_lock(&backtrace_lock);
|
||||
pr_warning("IPI backtrace for cpu %d\n", cpu);
|
||||
show_regs(regs);
|
||||
raw_spin_unlock(&backtrace_lock);
|
||||
cpu_clear(cpu, backtrace_mask);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Main handler for inter-processor interrupts
|
||||
*/
|
||||
|
|
@ -659,6 +713,10 @@ void handle_IPI(int ipinr, struct pt_regs *regs)
|
|||
irq_exit();
|
||||
break;
|
||||
|
||||
case IPI_CPU_BACKTRACE:
|
||||
ipi_cpu_backtrace(cpu, regs);
|
||||
break;
|
||||
|
||||
default:
|
||||
printk(KERN_CRIT "CPU%u: Unknown IPI message 0x%x\n",
|
||||
cpu, ipinr);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ obj-y := dma-mapping.o extable.o fault.o init.o \
|
|||
|
||||
obj-$(CONFIG_MMU) += fault-armv.o flush.o idmap.o ioremap.o \
|
||||
mmap.o pgd.o mmu.o
|
||||
obj-$(CONFIG_DEBUG_RODATA) += rodata.o
|
||||
|
||||
ifneq ($(CONFIG_MMU),y)
|
||||
obj-y += nommu.o
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ static void __iomem *l2x0_base;
|
|||
static DEFINE_RAW_SPINLOCK(l2x0_lock);
|
||||
static u32 l2x0_way_mask; /* Bitmask of active ways */
|
||||
static u32 l2x0_size;
|
||||
static u32 l2x0_cache_id;
|
||||
static unsigned int l2x0_sets;
|
||||
static unsigned int l2x0_ways;
|
||||
static unsigned long sync_reg_offset = L2X0_CACHE_SYNC;
|
||||
|
||||
/* Aurora don't have the cache ID register available, so we have to
|
||||
|
|
@ -49,6 +52,13 @@ struct l2x0_of_data {
|
|||
|
||||
static bool of_init = false;
|
||||
|
||||
static inline bool is_pl310_rev(int rev)
|
||||
{
|
||||
return (l2x0_cache_id &
|
||||
(L2X0_CACHE_ID_PART_MASK | L2X0_CACHE_ID_REV_MASK)) ==
|
||||
(L2X0_CACHE_ID_PART_L310 | rev);
|
||||
}
|
||||
|
||||
static inline void cache_wait_way(void __iomem *reg, unsigned long mask)
|
||||
{
|
||||
/* wait for cache operation by line or way to complete */
|
||||
|
|
@ -137,6 +147,23 @@ static void l2x0_cache_sync(void)
|
|||
raw_spin_unlock_irqrestore(&l2x0_lock, flags);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PL310_ERRATA_727915
|
||||
static void l2x0_for_each_set_way(void __iomem *reg)
|
||||
{
|
||||
int set;
|
||||
int way;
|
||||
unsigned long flags;
|
||||
|
||||
for (way = 0; way < l2x0_ways; way++) {
|
||||
raw_spin_lock_irqsave(&l2x0_lock, flags);
|
||||
for (set = 0; set < l2x0_sets; set++)
|
||||
writel_relaxed((way << 28) | (set << 5), reg);
|
||||
cache_sync();
|
||||
raw_spin_unlock_irqrestore(&l2x0_lock, flags);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void __l2x0_flush_all(void)
|
||||
{
|
||||
debug_writel(0x03);
|
||||
|
|
@ -150,6 +177,13 @@ static void l2x0_flush_all(void)
|
|||
{
|
||||
unsigned long flags;
|
||||
|
||||
#ifdef CONFIG_PL310_ERRATA_727915
|
||||
if (is_pl310_rev(REV_PL310_R2P0)) {
|
||||
l2x0_for_each_set_way(l2x0_base + L2X0_CLEAN_INV_LINE_IDX);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* clean all ways */
|
||||
raw_spin_lock_irqsave(&l2x0_lock, flags);
|
||||
__l2x0_flush_all();
|
||||
|
|
@ -160,11 +194,20 @@ static void l2x0_clean_all(void)
|
|||
{
|
||||
unsigned long flags;
|
||||
|
||||
#ifdef CONFIG_PL310_ERRATA_727915
|
||||
if (is_pl310_rev(REV_PL310_R2P0)) {
|
||||
l2x0_for_each_set_way(l2x0_base + L2X0_CLEAN_LINE_IDX);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* clean all ways */
|
||||
raw_spin_lock_irqsave(&l2x0_lock, flags);
|
||||
debug_writel(0x03);
|
||||
writel_relaxed(l2x0_way_mask, l2x0_base + L2X0_CLEAN_WAY);
|
||||
cache_wait_way(l2x0_base + L2X0_CLEAN_WAY, l2x0_way_mask);
|
||||
cache_sync();
|
||||
debug_writel(0x00);
|
||||
raw_spin_unlock_irqrestore(&l2x0_lock, flags);
|
||||
}
|
||||
|
||||
|
|
@ -323,65 +366,64 @@ static void l2x0_unlock(u32 cache_id)
|
|||
void __init l2x0_init(void __iomem *base, u32 aux_val, u32 aux_mask)
|
||||
{
|
||||
u32 aux;
|
||||
u32 cache_id;
|
||||
u32 way_size = 0;
|
||||
int ways;
|
||||
int way_size_shift = L2X0_WAY_SIZE_SHIFT;
|
||||
const char *type;
|
||||
|
||||
l2x0_base = base;
|
||||
if (cache_id_part_number_from_dt)
|
||||
cache_id = cache_id_part_number_from_dt;
|
||||
l2x0_cache_id = cache_id_part_number_from_dt;
|
||||
else
|
||||
cache_id = readl_relaxed(l2x0_base + L2X0_CACHE_ID);
|
||||
l2x0_cache_id = readl_relaxed(l2x0_base + L2X0_CACHE_ID);
|
||||
aux = readl_relaxed(l2x0_base + L2X0_AUX_CTRL);
|
||||
|
||||
aux &= aux_mask;
|
||||
aux |= aux_val;
|
||||
|
||||
/* Determine the number of ways */
|
||||
switch (cache_id & L2X0_CACHE_ID_PART_MASK) {
|
||||
switch (l2x0_cache_id & L2X0_CACHE_ID_PART_MASK) {
|
||||
case L2X0_CACHE_ID_PART_L310:
|
||||
if (aux & (1 << 16))
|
||||
ways = 16;
|
||||
l2x0_ways = 16;
|
||||
else
|
||||
ways = 8;
|
||||
l2x0_ways = 8;
|
||||
type = "L310";
|
||||
#ifdef CONFIG_PL310_ERRATA_753970
|
||||
/* Unmapped register. */
|
||||
sync_reg_offset = L2X0_DUMMY_REG;
|
||||
#endif
|
||||
if ((cache_id & L2X0_CACHE_ID_RTL_MASK) <= L2X0_CACHE_ID_RTL_R3P0)
|
||||
if ((l2x0_cache_id & L2X0_CACHE_ID_RTL_MASK) <= L2X0_CACHE_ID_RTL_R3P0)
|
||||
outer_cache.set_debug = pl310_set_debug;
|
||||
break;
|
||||
case L2X0_CACHE_ID_PART_L210:
|
||||
ways = (aux >> 13) & 0xf;
|
||||
l2x0_ways = (aux >> 13) & 0xf;
|
||||
type = "L210";
|
||||
break;
|
||||
|
||||
case AURORA_CACHE_ID:
|
||||
sync_reg_offset = AURORA_SYNC_REG;
|
||||
ways = (aux >> 13) & 0xf;
|
||||
ways = 2 << ((ways + 1) >> 2);
|
||||
l2x0_ways = (aux >> 13) & 0xf;
|
||||
l2x0_ways = 2 << ((l2x0_ways + 1) >> 2);
|
||||
way_size_shift = AURORA_WAY_SIZE_SHIFT;
|
||||
type = "Aurora";
|
||||
break;
|
||||
default:
|
||||
/* Assume unknown chips have 8 ways */
|
||||
ways = 8;
|
||||
l2x0_ways = 8;
|
||||
type = "L2x0 series";
|
||||
break;
|
||||
}
|
||||
|
||||
l2x0_way_mask = (1 << ways) - 1;
|
||||
l2x0_way_mask = (1 << l2x0_ways) - 1;
|
||||
|
||||
/*
|
||||
* L2 cache Size = Way size * Number of ways
|
||||
*/
|
||||
way_size = (aux & L2X0_AUX_CTRL_WAY_SIZE_MASK) >> 17;
|
||||
way_size = 1 << (way_size + way_size_shift);
|
||||
way_size = SZ_1K << (way_size + way_size_shift);
|
||||
|
||||
l2x0_size = ways * way_size * SZ_1K;
|
||||
l2x0_size = l2x0_ways * way_size;
|
||||
l2x0_sets = way_size / CACHE_LINE_SIZE;
|
||||
|
||||
/*
|
||||
* Check if l2x0 controller is already enabled.
|
||||
|
|
@ -390,7 +432,7 @@ void __init l2x0_init(void __iomem *base, u32 aux_val, u32 aux_mask)
|
|||
*/
|
||||
if (!(readl_relaxed(l2x0_base + L2X0_CTRL) & L2X0_CTRL_EN)) {
|
||||
/* Make sure that I&D is not locked down when starting */
|
||||
l2x0_unlock(cache_id);
|
||||
l2x0_unlock(l2x0_cache_id);
|
||||
|
||||
/* l2x0 controller is disabled */
|
||||
writel_relaxed(aux, l2x0_base + L2X0_AUX_CTRL);
|
||||
|
|
@ -419,7 +461,7 @@ void __init l2x0_init(void __iomem *base, u32 aux_val, u32 aux_mask)
|
|||
|
||||
printk(KERN_INFO "%s cache controller enabled\n", type);
|
||||
printk(KERN_INFO "l2x0: %d ways, CACHE_ID 0x%08x, AUX_CTRL 0x%08x, Cache size: %d B\n",
|
||||
ways, cache_id, aux, l2x0_size);
|
||||
l2x0_ways, l2x0_cache_id, aux, l2x0_size);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
|
|
|
|||
|
|
@ -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
|
||||
mov pc, 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
|
||||
|
|
|
|||
|
|
@ -276,10 +276,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 (in_atomic() || !mm)
|
||||
if (in_atomic() || irqs_disabled() || !mm)
|
||||
goto no_context;
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -595,11 +595,25 @@ static void __init *early_alloc(unsigned long sz)
|
|||
return early_alloc_aligned(sz, sz);
|
||||
}
|
||||
|
||||
static pte_t * __init early_pte_alloc(pmd_t *pmd, unsigned long addr, unsigned long prot)
|
||||
static pte_t * __init early_pte_alloc(pmd_t *pmd)
|
||||
{
|
||||
if (pmd_none(*pmd) || pmd_bad(*pmd))
|
||||
return early_alloc(PTE_HWTABLE_OFF + PTE_HWTABLE_SIZE);
|
||||
return pmd_page_vaddr(*pmd);
|
||||
}
|
||||
|
||||
static void __init early_pte_install(pmd_t *pmd, pte_t *pte, unsigned long prot)
|
||||
{
|
||||
__pmd_populate(pmd, __pa(pte), prot);
|
||||
BUG_ON(pmd_bad(*pmd));
|
||||
}
|
||||
|
||||
static pte_t * __init early_pte_alloc_and_install(pmd_t *pmd,
|
||||
unsigned long addr, unsigned long prot)
|
||||
{
|
||||
if (pmd_none(*pmd)) {
|
||||
pte_t *pte = early_alloc(PTE_HWTABLE_OFF + PTE_HWTABLE_SIZE);
|
||||
__pmd_populate(pmd, __pa(pte), prot);
|
||||
pte_t *pte = early_pte_alloc(pmd);
|
||||
early_pte_install(pmd, pte, prot);
|
||||
}
|
||||
BUG_ON(pmd_bad(*pmd));
|
||||
return pte_offset_kernel(pmd, addr);
|
||||
|
|
@ -609,11 +623,17 @@ static void __init alloc_init_pte(pmd_t *pmd, unsigned long addr,
|
|||
unsigned long end, unsigned long pfn,
|
||||
const struct mem_type *type)
|
||||
{
|
||||
pte_t *pte = early_pte_alloc(pmd, addr, type->prot_l1);
|
||||
pte_t *start_pte = early_pte_alloc(pmd);
|
||||
pte_t *pte = start_pte + pte_index(addr);
|
||||
|
||||
/* If replacing a section mapping, the whole section must be replaced */
|
||||
BUG_ON(!pmd_none(*pmd) && pmd_bad(*pmd) && ((addr | end) & ~PMD_MASK));
|
||||
|
||||
do {
|
||||
set_pte_ext(pte, pfn_pte(pfn, __pgprot(type->prot_pte)), 0);
|
||||
pfn++;
|
||||
} while (pte++, addr += PAGE_SIZE, addr != end);
|
||||
early_pte_install(pmd, start_pte, type->prot_l1);
|
||||
}
|
||||
|
||||
static void __init __map_init_section(pmd_t *pmd, unsigned long addr,
|
||||
|
|
@ -645,7 +665,8 @@ static void __init __map_init_section(pmd_t *pmd, unsigned long addr,
|
|||
|
||||
static void __init alloc_init_pmd(pud_t *pud, unsigned long addr,
|
||||
unsigned long end, phys_addr_t phys,
|
||||
const struct mem_type *type)
|
||||
const struct mem_type *type,
|
||||
bool force_pages)
|
||||
{
|
||||
pmd_t *pmd = pmd_offset(pud, addr);
|
||||
unsigned long next;
|
||||
|
|
@ -662,7 +683,8 @@ static void __init alloc_init_pmd(pud_t *pud, unsigned long addr,
|
|||
* aligned to a section boundary.
|
||||
*/
|
||||
if (type->prot_sect &&
|
||||
((addr | next | phys) & ~SECTION_MASK) == 0) {
|
||||
((addr | next | phys) & ~SECTION_MASK) == 0 &&
|
||||
!force_pages) {
|
||||
__map_init_section(pmd, addr, next, phys, type);
|
||||
} else {
|
||||
alloc_init_pte(pmd, addr, next,
|
||||
|
|
@ -675,14 +697,15 @@ static void __init alloc_init_pmd(pud_t *pud, unsigned long addr,
|
|||
}
|
||||
|
||||
static void __init alloc_init_pud(pgd_t *pgd, unsigned long addr,
|
||||
unsigned long end, unsigned long phys, const struct mem_type *type)
|
||||
unsigned long end, unsigned long phys, const struct mem_type *type,
|
||||
bool force_pages)
|
||||
{
|
||||
pud_t *pud = pud_offset(pgd, addr);
|
||||
unsigned long next;
|
||||
|
||||
do {
|
||||
next = pud_addr_end(addr, end);
|
||||
alloc_init_pmd(pud, addr, next, phys, type);
|
||||
alloc_init_pmd(pud, addr, next, phys, type, force_pages);
|
||||
phys += next - addr;
|
||||
} while (pud++, addr = next, addr != end);
|
||||
}
|
||||
|
|
@ -756,7 +779,7 @@ static void __init create_36bit_mapping(struct map_desc *md,
|
|||
* offsets, and we take full advantage of sections and
|
||||
* supersections.
|
||||
*/
|
||||
static void __init create_mapping(struct map_desc *md)
|
||||
static void __init create_mapping(struct map_desc *md, bool force_pages)
|
||||
{
|
||||
unsigned long addr, length, end;
|
||||
phys_addr_t phys;
|
||||
|
|
@ -806,7 +829,7 @@ static void __init create_mapping(struct map_desc *md)
|
|||
do {
|
||||
unsigned long next = pgd_addr_end(addr, end);
|
||||
|
||||
alloc_init_pud(pgd, addr, next, phys, type);
|
||||
alloc_init_pud(pgd, addr, next, phys, type, force_pages);
|
||||
|
||||
phys += next - addr;
|
||||
addr = next;
|
||||
|
|
@ -828,7 +851,7 @@ void __init iotable_init(struct map_desc *io_desc, int nr)
|
|||
svm = early_alloc_aligned(sizeof(*svm) * nr, __alignof__(*svm));
|
||||
|
||||
for (md = io_desc; nr; md++, nr--) {
|
||||
create_mapping(md);
|
||||
create_mapping(md, false);
|
||||
|
||||
vm = &svm->vm;
|
||||
vm->addr = (void *)(md->virtual & PAGE_MASK);
|
||||
|
|
@ -949,7 +972,7 @@ void __init debug_ll_io_init(void)
|
|||
map.virtual &= PAGE_MASK;
|
||||
map.length = PAGE_SIZE;
|
||||
map.type = MT_DEVICE;
|
||||
create_mapping(&map);
|
||||
create_mapping(&map, false);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
@ -994,6 +1017,28 @@ void __init sanity_check_meminfo(void)
|
|||
struct membank *bank = &meminfo.bank[j];
|
||||
*bank = meminfo.bank[i];
|
||||
|
||||
#ifdef CONFIG_SPARSEMEM
|
||||
if (pfn_to_section_nr(bank_pfn_start(bank)) !=
|
||||
pfn_to_section_nr(bank_pfn_end(bank) - 1)) {
|
||||
phys_addr_t sz;
|
||||
unsigned long start_pfn = bank_pfn_start(bank);
|
||||
unsigned long end_pfn = SECTION_ALIGN_UP(start_pfn + 1);
|
||||
sz = ((phys_addr_t)(end_pfn - start_pfn) << PAGE_SHIFT);
|
||||
|
||||
if (meminfo.nr_banks >= NR_BANKS) {
|
||||
pr_crit("NR_BANKS too low, ignoring %lld bytes of memory\n",
|
||||
(unsigned long long)(bank->size - sz));
|
||||
} else {
|
||||
memmove(bank + 1, bank,
|
||||
(meminfo.nr_banks - i) * sizeof(*bank));
|
||||
meminfo.nr_banks++;
|
||||
bank[1].size -= sz;
|
||||
bank[1].start = __pfn_to_phys(end_pfn);
|
||||
}
|
||||
bank->size = sz;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (bank->start > ULONG_MAX)
|
||||
highmem = 1;
|
||||
|
||||
|
|
@ -1191,7 +1236,7 @@ static void __init devicemaps_init(struct machine_desc *mdesc)
|
|||
map.virtual = MODULES_VADDR;
|
||||
map.length = ((unsigned long)_etext - map.virtual + ~SECTION_MASK) & SECTION_MASK;
|
||||
map.type = MT_ROM;
|
||||
create_mapping(&map);
|
||||
create_mapping(&map, false);
|
||||
#endif
|
||||
|
||||
/*
|
||||
|
|
@ -1202,14 +1247,14 @@ static void __init devicemaps_init(struct machine_desc *mdesc)
|
|||
map.virtual = FLUSH_BASE;
|
||||
map.length = SZ_1M;
|
||||
map.type = MT_CACHECLEAN;
|
||||
create_mapping(&map);
|
||||
create_mapping(&map, false);
|
||||
#endif
|
||||
#ifdef FLUSH_BASE_MINICACHE
|
||||
map.pfn = __phys_to_pfn(FLUSH_BASE_PHYS + SZ_1M);
|
||||
map.virtual = FLUSH_BASE_MINICACHE;
|
||||
map.length = SZ_1M;
|
||||
map.type = MT_MINICLEAN;
|
||||
create_mapping(&map);
|
||||
create_mapping(&map, false);
|
||||
#endif
|
||||
|
||||
/*
|
||||
|
|
@ -1221,12 +1266,12 @@ static void __init devicemaps_init(struct machine_desc *mdesc)
|
|||
map.virtual = 0xffff0000;
|
||||
map.length = PAGE_SIZE;
|
||||
map.type = MT_HIGH_VECTORS;
|
||||
create_mapping(&map);
|
||||
create_mapping(&map, false);
|
||||
|
||||
if (!vectors_high()) {
|
||||
map.virtual = 0;
|
||||
map.type = MT_LOW_VECTORS;
|
||||
create_mapping(&map);
|
||||
create_mapping(&map, false);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -1252,20 +1297,23 @@ static void __init devicemaps_init(struct machine_desc *mdesc)
|
|||
static void __init kmap_init(void)
|
||||
{
|
||||
#ifdef CONFIG_HIGHMEM
|
||||
pkmap_page_table = early_pte_alloc(pmd_off_k(PKMAP_BASE),
|
||||
pkmap_page_table = early_pte_alloc_and_install(pmd_off_k(PKMAP_BASE),
|
||||
PKMAP_BASE, _PAGE_KERNEL_TABLE);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static void __init map_lowmem(void)
|
||||
{
|
||||
struct memblock_region *reg;
|
||||
phys_addr_t start;
|
||||
phys_addr_t end;
|
||||
struct map_desc map;
|
||||
|
||||
/* Map all the lowmem memory banks. */
|
||||
for_each_memblock(memory, reg) {
|
||||
phys_addr_t start = reg->base;
|
||||
phys_addr_t end = start + reg->size;
|
||||
struct map_desc map;
|
||||
start = reg->base;
|
||||
end = start + reg->size;
|
||||
|
||||
if (end > arm_lowmem_limit)
|
||||
end = arm_lowmem_limit;
|
||||
|
|
@ -1277,8 +1325,20 @@ static void __init map_lowmem(void)
|
|||
map.length = end - start;
|
||||
map.type = MT_MEMORY;
|
||||
|
||||
create_mapping(&map);
|
||||
create_mapping(&map, false);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_RODATA
|
||||
start = __pa(_stext) & PMD_MASK;
|
||||
end = ALIGN(__pa(__end_rodata), PMD_SIZE);
|
||||
|
||||
map.pfn = __phys_to_pfn(start);
|
||||
map.virtual = __phys_to_virt(start);
|
||||
map.length = end - start;
|
||||
map.type = MT_MEMORY;
|
||||
|
||||
create_mapping(&map, true);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
159
arch/arm/mm/rodata.c
Normal file
159
arch/arm/mm/rodata.c
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* linux/arch/arm/mm/rodata.c
|
||||
*
|
||||
* Copyright (C) 2011 Google, Inc.
|
||||
*
|
||||
* Author: Colin Cross <ccross@android.com>
|
||||
*
|
||||
* Based on x86 implementation in arch/x86/mm/init_32.c
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include <asm/cache.h>
|
||||
#include <asm/pgtable.h>
|
||||
#include <asm/rodata.h>
|
||||
#include <asm/sections.h>
|
||||
#include <asm/tlbflush.h>
|
||||
|
||||
#include "mm.h"
|
||||
|
||||
static int kernel_set_to_readonly __read_mostly;
|
||||
|
||||
#ifdef CONFIG_DEBUG_RODATA_TEST
|
||||
static const int rodata_test_data = 0xC3;
|
||||
|
||||
static noinline void rodata_test(void)
|
||||
{
|
||||
int result;
|
||||
|
||||
pr_info("%s: attempting to write to read-only section:\n", __func__);
|
||||
|
||||
if (*(volatile int *)&rodata_test_data != 0xC3) {
|
||||
pr_err("read only data changed before test\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempt to to write to rodata_test_data, trapping the expected
|
||||
* data abort. If the trap executed, result will be 1. If it didn't,
|
||||
* result will be 0xFF.
|
||||
*/
|
||||
asm volatile(
|
||||
"0: str %[zero], [%[rodata_test_data]]\n"
|
||||
" mov %[result], #0xFF\n"
|
||||
" b 2f\n"
|
||||
"1: mov %[result], #1\n"
|
||||
"2:\n"
|
||||
|
||||
/* Exception fixup - if store at label 0 faults, jumps to 1 */
|
||||
".pushsection __ex_table, \"a\"\n"
|
||||
" .long 0b, 1b\n"
|
||||
".popsection\n"
|
||||
|
||||
: [result] "=r" (result)
|
||||
: [rodata_test_data] "r" (&rodata_test_data), [zero] "r" (0)
|
||||
: "memory"
|
||||
);
|
||||
|
||||
if (result == 1)
|
||||
pr_info("write to read-only section trapped, success\n");
|
||||
else
|
||||
pr_err("write to read-only section NOT trapped, test failed\n");
|
||||
|
||||
if (*(volatile int *)&rodata_test_data != 0xC3)
|
||||
pr_err("read only data changed during write\n");
|
||||
}
|
||||
#else
|
||||
static inline void rodata_test(void) { }
|
||||
#endif
|
||||
|
||||
static int set_page_attributes(unsigned long virt, int numpages,
|
||||
pte_t (*f)(pte_t))
|
||||
{
|
||||
pmd_t *pmd;
|
||||
pte_t *pte;
|
||||
unsigned long start = virt;
|
||||
unsigned long end = virt + (numpages << PAGE_SHIFT);
|
||||
unsigned long pmd_end;
|
||||
|
||||
while (virt < end) {
|
||||
pmd = pmd_off_k(virt);
|
||||
pmd_end = min(ALIGN(virt + 1, PMD_SIZE), end);
|
||||
|
||||
if ((pmd_val(*pmd) & PMD_TYPE_MASK) != PMD_TYPE_TABLE) {
|
||||
pr_err("%s: pmd %p=%08lx for %08lx not page table\n",
|
||||
__func__, pmd, pmd_val(*pmd), virt);
|
||||
virt = pmd_end;
|
||||
continue;
|
||||
}
|
||||
|
||||
while (virt < pmd_end) {
|
||||
pte = pte_offset_kernel(pmd, virt);
|
||||
set_pte_ext(pte, f(*pte), 0);
|
||||
virt += PAGE_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
flush_tlb_kernel_range(start, end);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int set_memory_ro(unsigned long virt, int numpages)
|
||||
{
|
||||
return set_page_attributes(virt, numpages, pte_wrprotect);
|
||||
}
|
||||
EXPORT_SYMBOL(set_memory_ro);
|
||||
|
||||
int set_memory_rw(unsigned long virt, int numpages)
|
||||
{
|
||||
return set_page_attributes(virt, numpages, pte_mkwrite);
|
||||
}
|
||||
EXPORT_SYMBOL(set_memory_rw);
|
||||
|
||||
void set_kernel_text_rw(void)
|
||||
{
|
||||
unsigned long start = PAGE_ALIGN((unsigned long)_text);
|
||||
unsigned long size = PAGE_ALIGN((unsigned long)__end_rodata) - start;
|
||||
|
||||
if (!kernel_set_to_readonly)
|
||||
return;
|
||||
|
||||
pr_debug("Set kernel text: %lx - %lx to read-write\n",
|
||||
start, start + size);
|
||||
|
||||
set_memory_rw(start, size >> PAGE_SHIFT);
|
||||
}
|
||||
|
||||
void set_kernel_text_ro(void)
|
||||
{
|
||||
unsigned long start = PAGE_ALIGN((unsigned long)_text);
|
||||
unsigned long size = PAGE_ALIGN((unsigned long)__end_rodata) - start;
|
||||
|
||||
if (!kernel_set_to_readonly)
|
||||
return;
|
||||
|
||||
pr_info_once("Write protecting the kernel text section %lx - %lx\n",
|
||||
start, start + size);
|
||||
|
||||
pr_debug("Set kernel text: %lx - %lx to read only\n",
|
||||
start, start + size);
|
||||
|
||||
set_memory_ro(start, size >> PAGE_SHIFT);
|
||||
}
|
||||
|
||||
void mark_rodata_ro(void)
|
||||
{
|
||||
kernel_set_to_readonly = 1;
|
||||
|
||||
set_kernel_text_ro();
|
||||
|
||||
rodata_test();
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -40,19 +40,6 @@ DEFINE_PER_CPU_SHARED_ALIGNED(struct tss_struct, init_tss) = INIT_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
|
||||
|
||||
struct kmem_cache *task_xstate_cachep;
|
||||
|
|
@ -257,14 +244,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 */
|
||||
|
|
|
|||
|
|
@ -1107,6 +1107,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",
|
||||
};
|
||||
|
|
@ -1126,6 +1142,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)
|
||||
|
|
|
|||
|
|
@ -100,6 +100,8 @@ source "drivers/memstick/Kconfig"
|
|||
|
||||
source "drivers/leds/Kconfig"
|
||||
|
||||
source "drivers/switch/Kconfig"
|
||||
|
||||
source "drivers/accessibility/Kconfig"
|
||||
|
||||
source "drivers/infiniband/Kconfig"
|
||||
|
|
|
|||
|
|
@ -111,6 +111,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/
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@
|
|||
#include <linux/async.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <linux/cpuidle.h>
|
||||
#include <linux/timer.h>
|
||||
|
||||
#include "../base.h"
|
||||
#include "power.h"
|
||||
|
||||
|
|
@ -54,6 +56,12 @@ struct suspend_stats suspend_stats;
|
|||
static DEFINE_MUTEX(dpm_list_mtx);
|
||||
static pm_message_t pm_transition;
|
||||
|
||||
struct dpm_watchdog {
|
||||
struct device *dev;
|
||||
struct task_struct *tsk;
|
||||
struct timer_list timer;
|
||||
};
|
||||
|
||||
static int async_error;
|
||||
|
||||
/**
|
||||
|
|
@ -384,6 +392,56 @@ static int dpm_run_callback(pm_callback_t cb, struct device *dev,
|
|||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* dpm_wd_handler - Driver suspend / resume watchdog handler.
|
||||
*
|
||||
* 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_wd_handler(unsigned long data)
|
||||
{
|
||||
struct dpm_watchdog *wd = (void *)data;
|
||||
struct device *dev = wd->dev;
|
||||
struct task_struct *tsk = wd->tsk;
|
||||
|
||||
dev_emerg(dev, "**** DPM device timeout ****\n");
|
||||
show_stack(tsk, NULL);
|
||||
|
||||
BUG();
|
||||
}
|
||||
|
||||
/**
|
||||
* dpm_wd_set - Enable pm watchdog for given device.
|
||||
* @wd: Watchdog. Must be allocated on the stack.
|
||||
* @dev: Device to handle.
|
||||
*/
|
||||
static void dpm_wd_set(struct dpm_watchdog *wd, struct device *dev)
|
||||
{
|
||||
struct timer_list *timer = &wd->timer;
|
||||
|
||||
wd->dev = dev;
|
||||
wd->tsk = get_current();
|
||||
|
||||
init_timer_on_stack(timer);
|
||||
timer->expires = jiffies + HZ * 12;
|
||||
timer->function = dpm_wd_handler;
|
||||
timer->data = (unsigned long)wd;
|
||||
add_timer(timer);
|
||||
}
|
||||
|
||||
/**
|
||||
* dpm_wd_clear - Disable pm watchdog.
|
||||
* @wd: Watchdog to disable.
|
||||
*/
|
||||
static void dpm_wd_clear(struct dpm_watchdog *wd)
|
||||
{
|
||||
struct timer_list *timer = &wd->timer;
|
||||
|
||||
del_timer_sync(timer);
|
||||
destroy_timer_on_stack(timer);
|
||||
}
|
||||
|
||||
/*------------------------- Resume routines -------------------------*/
|
||||
|
||||
/**
|
||||
|
|
@ -570,6 +628,7 @@ static int device_resume(struct device *dev, pm_message_t state, bool async)
|
|||
pm_callback_t callback = NULL;
|
||||
char *info = NULL;
|
||||
int error = 0;
|
||||
struct dpm_watchdog wd;
|
||||
|
||||
TRACE_DEVICE(dev);
|
||||
TRACE_RESUME(0);
|
||||
|
|
@ -585,6 +644,7 @@ static int device_resume(struct device *dev, pm_message_t state, bool async)
|
|||
* a resumed device, even if the device hasn't been completed yet.
|
||||
*/
|
||||
dev->power.is_prepared = false;
|
||||
dpm_wd_set(&wd, dev);
|
||||
|
||||
if (!dev->power.is_suspended)
|
||||
goto Unlock;
|
||||
|
|
@ -636,6 +696,7 @@ static int device_resume(struct device *dev, pm_message_t state, bool async)
|
|||
|
||||
Unlock:
|
||||
device_unlock(dev);
|
||||
dpm_wd_clear(&wd);
|
||||
|
||||
Complete:
|
||||
complete_all(&dev->power.completion);
|
||||
|
|
@ -1053,6 +1114,7 @@ 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 dpm_watchdog wd;
|
||||
|
||||
dpm_wait_for_children(dev, async);
|
||||
|
||||
|
|
@ -1075,6 +1137,8 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
|
|||
|
||||
if (dev->power.syscore)
|
||||
goto Complete;
|
||||
|
||||
dpm_wd_set(&wd, dev);
|
||||
|
||||
device_lock(dev);
|
||||
|
||||
|
|
@ -1131,6 +1195,8 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
|
|||
|
||||
device_unlock(dev);
|
||||
|
||||
dpm_wd_clear(&wd);
|
||||
|
||||
Complete:
|
||||
complete_all(&dev->power.completion);
|
||||
if (error)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,19 @@ menu "Character devices"
|
|||
|
||||
source "drivers/tty/Kconfig"
|
||||
|
||||
config DEVMEM
|
||||
bool "Memory device driver"
|
||||
default y
|
||||
help
|
||||
The memory driver provides two character devices, mem and kmem, which
|
||||
provide access to the system's memory. The mem device is a view of
|
||||
physical memory, and each byte in the device corresponds to the
|
||||
matching physical address. The kmem device is the same as mem, but
|
||||
the addresses correspond to the kernel's virtual address space rather
|
||||
than physical memory. These devices are standard parts of a Linux
|
||||
system and most users should say Y here. You might say N if very
|
||||
security conscience or memory is tight.
|
||||
|
||||
config DEVKMEM
|
||||
bool "/dev/kmem virtual device support"
|
||||
default y
|
||||
|
|
@ -584,6 +597,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 MSM_SMD_PKT
|
||||
|
|
|
|||
|
|
@ -56,6 +56,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");
|
||||
|
||||
static spinlock_t g_dcc_tty_lock = SPIN_LOCK_UNLOCKED;
|
||||
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, &ch, 1);
|
||||
tty_flip_buffer_push(g_dcc_tty);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
|
||||
|
||||
|
|
@ -60,6 +60,7 @@ static inline int valid_mmap_phys_addr_range(unsigned long pfn, size_t size)
|
|||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_DEVMEM) || defined(CONFIG_DEVKMEM)
|
||||
#ifdef CONFIG_STRICT_DEVMEM
|
||||
static inline int range_is_allowed(unsigned long pfn, unsigned long size)
|
||||
{
|
||||
|
|
@ -85,7 +86,9 @@ static inline int range_is_allowed(unsigned long pfn, unsigned long size)
|
|||
return 1;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DEVMEM
|
||||
void __weak unxlate_dev_mem_ptr(unsigned long phys, void *addr)
|
||||
{
|
||||
}
|
||||
|
|
@ -212,6 +215,9 @@ static ssize_t write_mem(struct file *file, const char __user *buf,
|
|||
*ppos += written;
|
||||
return written;
|
||||
}
|
||||
#endif /* CONFIG_DEVMEM */
|
||||
|
||||
#if defined(CONFIG_DEVMEM) || defined(CONFIG_DEVKMEM)
|
||||
|
||||
int __weak phys_mem_access_prot_allowed(struct file *file,
|
||||
unsigned long pfn, unsigned long size, pgprot_t *vma_prot)
|
||||
|
|
@ -333,6 +339,7 @@ static int mmap_mem(struct file *file, struct vm_area_struct *vma)
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_DEVMEM */
|
||||
|
||||
#ifdef CONFIG_DEVKMEM
|
||||
static int mmap_kmem(struct file *file, struct vm_area_struct *vma)
|
||||
|
|
@ -727,6 +734,8 @@ static loff_t null_lseek(struct file *file, loff_t offset, int orig)
|
|||
return file->f_pos = 0;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_DEVMEM) || defined(CONFIG_DEVKMEM) || defined(CONFIG_DEVPORT)
|
||||
|
||||
/*
|
||||
* The memory devices use the full 32/64 bits of the offset, and so we cannot
|
||||
* check against negative addresses: they are ok. The return value is weird,
|
||||
|
|
@ -760,10 +769,14 @@ static loff_t memory_lseek(struct file *file, loff_t offset, int orig)
|
|||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_DEVMEM) || defined(CONFIG_DEVKMEM) || defined(CONFIG_DEVPORT)
|
||||
static int open_port(struct inode *inode, struct file *filp)
|
||||
{
|
||||
return capable(CAP_SYS_RAWIO) ? 0 : -EPERM;
|
||||
}
|
||||
#endif
|
||||
|
||||
#define zero_lseek null_lseek
|
||||
#define full_lseek null_lseek
|
||||
|
|
@ -774,6 +787,7 @@ static int open_port(struct inode *inode, struct file *filp)
|
|||
#define open_kmem open_mem
|
||||
#define open_oldmem open_mem
|
||||
|
||||
#ifdef CONFIG_DEVMEM
|
||||
static const struct file_operations mem_fops = {
|
||||
.llseek = memory_lseek,
|
||||
.read = read_mem,
|
||||
|
|
@ -782,6 +796,7 @@ static const struct file_operations mem_fops = {
|
|||
.open = open_mem,
|
||||
.get_unmapped_area = get_unmapped_area_mem,
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DEVKMEM
|
||||
static const struct file_operations kmem_fops = {
|
||||
|
|
@ -851,7 +866,9 @@ static const struct memdev {
|
|||
const struct file_operations *fops;
|
||||
struct backing_dev_info *dev_info;
|
||||
} devlist[] = {
|
||||
#ifdef CONFIG_DEVMEM
|
||||
[1] = { "mem", 0, &mem_fops, &directly_mappable_cdev_bdi },
|
||||
#endif
|
||||
#ifdef CONFIG_DEVKMEM
|
||||
[2] = { "kmem", 0, &kmem_fops, &directly_mappable_cdev_bdi },
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -160,6 +170,23 @@ config CPU_FREQ_GOV_ONDEMAND
|
|||
|
||||
If in doubt, say N.
|
||||
|
||||
config CPU_FREQ_GOV_INTERACTIVE
|
||||
tristate "'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.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called cpufreq_interactive.
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -9,6 +9,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
|
||||
|
||||
# CPUfreq cross-arch helpers
|
||||
|
|
|
|||
1247
drivers/cpufreq/cpufreq_interactive.c
Normal file
1247
drivers/cpufreq/cpufreq_interactive.c
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -341,6 +341,27 @@ static int cpufreq_stat_notifier_trans(struct notifier_block *nb,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int cpufreq_stats_create_table_cpu(unsigned int cpu)
|
||||
{
|
||||
struct cpufreq_policy *policy;
|
||||
struct cpufreq_frequency_table *table;
|
||||
int ret = -ENODEV;
|
||||
|
||||
policy = cpufreq_cpu_get(cpu);
|
||||
if (!policy)
|
||||
return -ENODEV;
|
||||
|
||||
table = cpufreq_frequency_get_table(cpu);
|
||||
if (!table)
|
||||
goto out;
|
||||
|
||||
ret = cpufreq_stats_create_table(policy, table);
|
||||
|
||||
out:
|
||||
cpufreq_cpu_put(policy);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __cpuinit cpufreq_stat_cpu_callback(struct notifier_block *nfb,
|
||||
unsigned long action,
|
||||
void *hcpu)
|
||||
|
|
@ -362,6 +383,10 @@ static int __cpuinit cpufreq_stat_cpu_callback(struct notifier_block *nfb,
|
|||
cpufreq_stats_free_sysfs(cpu);
|
||||
cpufreq_stats_free_table(cpu);
|
||||
break;
|
||||
case CPU_DOWN_FAILED:
|
||||
case CPU_DOWN_FAILED_FROZEN:
|
||||
cpufreq_stats_create_table_cpu(cpu);
|
||||
break;
|
||||
}
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -187,7 +187,12 @@ static inline int performance_multiplier(void)
|
|||
|
||||
/* for higher loadavg, we are more reluctant */
|
||||
|
||||
mult += 2 * get_loadavg();
|
||||
/*
|
||||
* 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_iowait_cpu(smp_processor_id());
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
obj-y += drm/ vga/
|
||||
obj-y += drm/ vga/ ion/
|
||||
obj-$(CONFIG_TEGRA_HOST1X) += host1x/
|
||||
|
|
|
|||
14
drivers/gpu/ion/Kconfig
Normal file
14
drivers/gpu/ion/Kconfig
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
menuconfig ION
|
||||
tristate "Ion Memory Manager"
|
||||
depends on ARM
|
||||
select GENERIC_ALLOCATOR
|
||||
select DMA_SHARED_BUFFER
|
||||
help
|
||||
Chose this option to enable the ION Memory Manager.
|
||||
|
||||
config ION_TEGRA
|
||||
tristate "Ion for Tegra"
|
||||
depends on ARCH_TEGRA && ION
|
||||
help
|
||||
Choose this option if you wish to use ion on an nVidia Tegra.
|
||||
|
||||
3
drivers/gpu/ion/Makefile
Normal file
3
drivers/gpu/ion/Makefile
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
obj-$(CONFIG_ION) += ion.o ion_heap.o ion_page_pool.o ion_system_heap.o \
|
||||
ion_carveout_heap.o ion_chunk_heap.o
|
||||
obj-$(CONFIG_ION_TEGRA) += tegra/
|
||||
1437
drivers/gpu/ion/ion.c
Normal file
1437
drivers/gpu/ion/ion.c
Normal file
File diff suppressed because it is too large
Load Diff
182
drivers/gpu/ion/ion_carveout_heap.c
Normal file
182
drivers/gpu/ion/ion_carveout_heap.c
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* drivers/gpu/ion/ion_carveout_heap.c
|
||||
*
|
||||
* Copyright (C) 2011 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/spinlock.h>
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/genalloc.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/ion.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/scatterlist.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include "ion_priv.h"
|
||||
|
||||
#include <asm/mach/map.h>
|
||||
|
||||
struct ion_carveout_heap {
|
||||
struct ion_heap heap;
|
||||
struct gen_pool *pool;
|
||||
ion_phys_addr_t base;
|
||||
};
|
||||
|
||||
ion_phys_addr_t ion_carveout_allocate(struct ion_heap *heap,
|
||||
unsigned long size,
|
||||
unsigned long align)
|
||||
{
|
||||
struct ion_carveout_heap *carveout_heap =
|
||||
container_of(heap, struct ion_carveout_heap, heap);
|
||||
unsigned long offset = gen_pool_alloc(carveout_heap->pool, size);
|
||||
|
||||
if (!offset)
|
||||
return ION_CARVEOUT_ALLOCATE_FAIL;
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
void ion_carveout_free(struct ion_heap *heap, ion_phys_addr_t addr,
|
||||
unsigned long size)
|
||||
{
|
||||
struct ion_carveout_heap *carveout_heap =
|
||||
container_of(heap, struct ion_carveout_heap, heap);
|
||||
|
||||
if (addr == ION_CARVEOUT_ALLOCATE_FAIL)
|
||||
return;
|
||||
gen_pool_free(carveout_heap->pool, addr, size);
|
||||
}
|
||||
|
||||
static int ion_carveout_heap_phys(struct ion_heap *heap,
|
||||
struct ion_buffer *buffer,
|
||||
ion_phys_addr_t *addr, size_t *len)
|
||||
{
|
||||
*addr = buffer->priv_phys;
|
||||
*len = buffer->size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ion_carveout_heap_allocate(struct ion_heap *heap,
|
||||
struct ion_buffer *buffer,
|
||||
unsigned long size, unsigned long align,
|
||||
unsigned long flags)
|
||||
{
|
||||
buffer->priv_phys = ion_carveout_allocate(heap, size, align);
|
||||
return buffer->priv_phys == ION_CARVEOUT_ALLOCATE_FAIL ? -ENOMEM : 0;
|
||||
}
|
||||
|
||||
static void ion_carveout_heap_free(struct ion_buffer *buffer)
|
||||
{
|
||||
struct ion_heap *heap = buffer->heap;
|
||||
|
||||
ion_carveout_free(heap, buffer->priv_phys, buffer->size);
|
||||
buffer->priv_phys = ION_CARVEOUT_ALLOCATE_FAIL;
|
||||
}
|
||||
|
||||
struct sg_table *ion_carveout_heap_map_dma(struct ion_heap *heap,
|
||||
struct ion_buffer *buffer)
|
||||
{
|
||||
struct sg_table *table;
|
||||
int ret;
|
||||
|
||||
table = kzalloc(sizeof(struct sg_table), GFP_KERNEL);
|
||||
if (!table)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
ret = sg_alloc_table(table, 1, GFP_KERNEL);
|
||||
if (ret) {
|
||||
kfree(table);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
sg_set_page(table->sgl, phys_to_page(buffer->priv_phys), buffer->size,
|
||||
0);
|
||||
return table;
|
||||
}
|
||||
|
||||
void ion_carveout_heap_unmap_dma(struct ion_heap *heap,
|
||||
struct ion_buffer *buffer)
|
||||
{
|
||||
sg_free_table(buffer->sg_table);
|
||||
}
|
||||
|
||||
void *ion_carveout_heap_map_kernel(struct ion_heap *heap,
|
||||
struct ion_buffer *buffer)
|
||||
{
|
||||
int mtype = MT_MEMORY_NONCACHED;
|
||||
|
||||
if (buffer->flags & ION_FLAG_CACHED)
|
||||
mtype = MT_MEMORY;
|
||||
|
||||
return __arm_ioremap(buffer->priv_phys, buffer->size,
|
||||
mtype);
|
||||
}
|
||||
|
||||
void ion_carveout_heap_unmap_kernel(struct ion_heap *heap,
|
||||
struct ion_buffer *buffer)
|
||||
{
|
||||
__arm_iounmap(buffer->vaddr);
|
||||
buffer->vaddr = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
int ion_carveout_heap_map_user(struct ion_heap *heap, struct ion_buffer *buffer,
|
||||
struct vm_area_struct *vma)
|
||||
{
|
||||
return remap_pfn_range(vma, vma->vm_start,
|
||||
__phys_to_pfn(buffer->priv_phys) + vma->vm_pgoff,
|
||||
vma->vm_end - vma->vm_start,
|
||||
pgprot_noncached(vma->vm_page_prot));
|
||||
}
|
||||
|
||||
static struct ion_heap_ops carveout_heap_ops = {
|
||||
.allocate = ion_carveout_heap_allocate,
|
||||
.free = ion_carveout_heap_free,
|
||||
.phys = ion_carveout_heap_phys,
|
||||
.map_dma = ion_carveout_heap_map_dma,
|
||||
.unmap_dma = ion_carveout_heap_unmap_dma,
|
||||
.map_user = ion_carveout_heap_map_user,
|
||||
.map_kernel = ion_carveout_heap_map_kernel,
|
||||
.unmap_kernel = ion_carveout_heap_unmap_kernel,
|
||||
};
|
||||
|
||||
struct ion_heap *ion_carveout_heap_create(struct ion_platform_heap *heap_data)
|
||||
{
|
||||
struct ion_carveout_heap *carveout_heap;
|
||||
|
||||
carveout_heap = kzalloc(sizeof(struct ion_carveout_heap), GFP_KERNEL);
|
||||
if (!carveout_heap)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
carveout_heap->pool = gen_pool_create(12, -1);
|
||||
if (!carveout_heap->pool) {
|
||||
kfree(carveout_heap);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
carveout_heap->base = heap_data->base;
|
||||
gen_pool_add(carveout_heap->pool, carveout_heap->base, heap_data->size,
|
||||
-1);
|
||||
carveout_heap->heap.ops = &carveout_heap_ops;
|
||||
carveout_heap->heap.type = ION_HEAP_TYPE_CARVEOUT;
|
||||
|
||||
return &carveout_heap->heap;
|
||||
}
|
||||
|
||||
void ion_carveout_heap_destroy(struct ion_heap *heap)
|
||||
{
|
||||
struct ion_carveout_heap *carveout_heap =
|
||||
container_of(heap, struct ion_carveout_heap, heap);
|
||||
|
||||
gen_pool_destroy(carveout_heap->pool);
|
||||
kfree(carveout_heap);
|
||||
carveout_heap = NULL;
|
||||
}
|
||||
210
drivers/gpu/ion/ion_chunk_heap.c
Normal file
210
drivers/gpu/ion/ion_chunk_heap.c
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
* drivers/gpu/ion/ion_chunk_heap.c
|
||||
*
|
||||
* Copyright (C) 2012 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/spinlock.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/genalloc.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/ion.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/scatterlist.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include "ion_priv.h"
|
||||
|
||||
#include <asm/mach/map.h>
|
||||
|
||||
struct ion_chunk_heap {
|
||||
struct ion_heap heap;
|
||||
struct gen_pool *pool;
|
||||
ion_phys_addr_t base;
|
||||
unsigned long chunk_size;
|
||||
unsigned long size;
|
||||
unsigned long allocated;
|
||||
};
|
||||
|
||||
static int ion_chunk_heap_allocate(struct ion_heap *heap,
|
||||
struct ion_buffer *buffer,
|
||||
unsigned long size, unsigned long align,
|
||||
unsigned long flags)
|
||||
{
|
||||
struct ion_chunk_heap *chunk_heap =
|
||||
container_of(heap, struct ion_chunk_heap, heap);
|
||||
struct sg_table *table;
|
||||
struct scatterlist *sg;
|
||||
int ret, i;
|
||||
unsigned long num_chunks;
|
||||
|
||||
if (ion_buffer_fault_user_mappings(buffer))
|
||||
return -ENOMEM;
|
||||
|
||||
num_chunks = ALIGN(size, chunk_heap->chunk_size) /
|
||||
chunk_heap->chunk_size;
|
||||
buffer->size = num_chunks * chunk_heap->chunk_size;
|
||||
|
||||
if (buffer->size > chunk_heap->size - chunk_heap->allocated)
|
||||
return -ENOMEM;
|
||||
|
||||
table = kzalloc(sizeof(struct sg_table), GFP_KERNEL);
|
||||
if (!table)
|
||||
return -ENOMEM;
|
||||
ret = sg_alloc_table(table, num_chunks, GFP_KERNEL);
|
||||
if (ret) {
|
||||
kfree(table);
|
||||
return ret;
|
||||
}
|
||||
|
||||
sg = table->sgl;
|
||||
for (i = 0; i < num_chunks; i++) {
|
||||
unsigned long paddr = gen_pool_alloc(chunk_heap->pool,
|
||||
chunk_heap->chunk_size);
|
||||
if (!paddr)
|
||||
goto err;
|
||||
sg_set_page(sg, phys_to_page(paddr), chunk_heap->chunk_size, 0);
|
||||
sg = sg_next(sg);
|
||||
}
|
||||
|
||||
buffer->priv_virt = table;
|
||||
chunk_heap->allocated += buffer->size;
|
||||
return 0;
|
||||
err:
|
||||
sg = table->sgl;
|
||||
for (i -= 1; i >= 0; i--) {
|
||||
gen_pool_free(chunk_heap->pool, page_to_phys(sg_page(sg)),
|
||||
sg_dma_len(sg));
|
||||
sg = sg_next(sg);
|
||||
}
|
||||
sg_free_table(table);
|
||||
kfree(table);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
static void ion_chunk_heap_free(struct ion_buffer *buffer)
|
||||
{
|
||||
struct ion_heap *heap = buffer->heap;
|
||||
struct ion_chunk_heap *chunk_heap =
|
||||
container_of(heap, struct ion_chunk_heap, heap);
|
||||
struct sg_table *table = buffer->priv_virt;
|
||||
struct scatterlist *sg;
|
||||
int i;
|
||||
|
||||
ion_heap_buffer_zero(buffer);
|
||||
|
||||
for_each_sg(table->sgl, sg, table->nents, i) {
|
||||
if (ion_buffer_cached(buffer))
|
||||
arm_dma_ops.sync_single_for_device(NULL,
|
||||
pfn_to_dma(NULL, page_to_pfn(sg_page(sg))),
|
||||
sg_dma_len(sg), DMA_BIDIRECTIONAL);
|
||||
gen_pool_free(chunk_heap->pool, page_to_phys(sg_page(sg)),
|
||||
sg_dma_len(sg));
|
||||
}
|
||||
chunk_heap->allocated -= buffer->size;
|
||||
sg_free_table(table);
|
||||
kfree(table);
|
||||
}
|
||||
|
||||
struct sg_table *ion_chunk_heap_map_dma(struct ion_heap *heap,
|
||||
struct ion_buffer *buffer)
|
||||
{
|
||||
return buffer->priv_virt;
|
||||
}
|
||||
|
||||
void ion_chunk_heap_unmap_dma(struct ion_heap *heap,
|
||||
struct ion_buffer *buffer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
static struct ion_heap_ops chunk_heap_ops = {
|
||||
.allocate = ion_chunk_heap_allocate,
|
||||
.free = ion_chunk_heap_free,
|
||||
.map_dma = ion_chunk_heap_map_dma,
|
||||
.unmap_dma = ion_chunk_heap_unmap_dma,
|
||||
.map_user = ion_heap_map_user,
|
||||
.map_kernel = ion_heap_map_kernel,
|
||||
.unmap_kernel = ion_heap_unmap_kernel,
|
||||
};
|
||||
|
||||
struct ion_heap *ion_chunk_heap_create(struct ion_platform_heap *heap_data)
|
||||
{
|
||||
struct ion_chunk_heap *chunk_heap;
|
||||
struct vm_struct *vm_struct;
|
||||
pgprot_t pgprot = pgprot_writecombine(PAGE_KERNEL);
|
||||
int i, ret;
|
||||
|
||||
|
||||
chunk_heap = kzalloc(sizeof(struct ion_chunk_heap), GFP_KERNEL);
|
||||
if (!chunk_heap)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
chunk_heap->chunk_size = (unsigned long)heap_data->priv;
|
||||
chunk_heap->pool = gen_pool_create(get_order(chunk_heap->chunk_size) +
|
||||
PAGE_SHIFT, -1);
|
||||
if (!chunk_heap->pool) {
|
||||
ret = -ENOMEM;
|
||||
goto error_gen_pool_create;
|
||||
}
|
||||
chunk_heap->base = heap_data->base;
|
||||
chunk_heap->size = heap_data->size;
|
||||
chunk_heap->allocated = 0;
|
||||
|
||||
vm_struct = get_vm_area(PAGE_SIZE, VM_ALLOC);
|
||||
if (!vm_struct) {
|
||||
ret = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
for (i = 0; i < chunk_heap->size; i += PAGE_SIZE) {
|
||||
struct page *page = phys_to_page(chunk_heap->base + i);
|
||||
struct page **pages = &page;
|
||||
|
||||
ret = map_vm_area(vm_struct, pgprot, &pages);
|
||||
if (ret)
|
||||
goto error_map_vm_area;
|
||||
memset(vm_struct->addr, 0, PAGE_SIZE);
|
||||
unmap_kernel_range((unsigned long)vm_struct->addr, PAGE_SIZE);
|
||||
}
|
||||
free_vm_area(vm_struct);
|
||||
|
||||
arm_dma_ops.sync_single_for_device(NULL,
|
||||
pfn_to_dma(NULL, page_to_pfn(phys_to_page(heap_data->base))),
|
||||
heap_data->size, DMA_BIDIRECTIONAL);
|
||||
gen_pool_add(chunk_heap->pool, chunk_heap->base, heap_data->size, -1);
|
||||
chunk_heap->heap.ops = &chunk_heap_ops;
|
||||
chunk_heap->heap.type = ION_HEAP_TYPE_CHUNK;
|
||||
chunk_heap->heap.flags = ION_HEAP_FLAG_DEFER_FREE;
|
||||
pr_info("%s: base %lu size %u align %ld\n", __func__, chunk_heap->base,
|
||||
heap_data->size, heap_data->align);
|
||||
|
||||
return &chunk_heap->heap;
|
||||
|
||||
error_map_vm_area:
|
||||
free_vm_area(vm_struct);
|
||||
error:
|
||||
gen_pool_destroy(chunk_heap->pool);
|
||||
error_gen_pool_create:
|
||||
kfree(chunk_heap);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
void ion_chunk_heap_destroy(struct ion_heap *heap)
|
||||
{
|
||||
struct ion_chunk_heap *chunk_heap =
|
||||
container_of(heap, struct ion_chunk_heap, heap);
|
||||
|
||||
gen_pool_destroy(chunk_heap->pool);
|
||||
kfree(chunk_heap);
|
||||
chunk_heap = NULL;
|
||||
}
|
||||
297
drivers/gpu/ion/ion_heap.c
Normal file
297
drivers/gpu/ion/ion_heap.c
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
/*
|
||||
* drivers/gpu/ion/ion_heap.c
|
||||
*
|
||||
* Copyright (C) 2011 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/err.h>
|
||||
#include <linux/freezer.h>
|
||||
#include <linux/ion.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/rtmutex.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/scatterlist.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include "ion_priv.h"
|
||||
|
||||
void *ion_heap_map_kernel(struct ion_heap *heap,
|
||||
struct ion_buffer *buffer)
|
||||
{
|
||||
struct scatterlist *sg;
|
||||
int i, j;
|
||||
void *vaddr;
|
||||
pgprot_t pgprot;
|
||||
struct sg_table *table = buffer->sg_table;
|
||||
int npages = PAGE_ALIGN(buffer->size) / PAGE_SIZE;
|
||||
struct page **pages = vmalloc(sizeof(struct page *) * npages);
|
||||
struct page **tmp = pages;
|
||||
|
||||
if (!pages)
|
||||
return 0;
|
||||
|
||||
if (buffer->flags & ION_FLAG_CACHED)
|
||||
pgprot = PAGE_KERNEL;
|
||||
else
|
||||
pgprot = pgprot_writecombine(PAGE_KERNEL);
|
||||
|
||||
for_each_sg(table->sgl, sg, table->nents, i) {
|
||||
int npages_this_entry = PAGE_ALIGN(sg_dma_len(sg)) / PAGE_SIZE;
|
||||
struct page *page = sg_page(sg);
|
||||
BUG_ON(i >= npages);
|
||||
for (j = 0; j < npages_this_entry; j++) {
|
||||
*(tmp++) = page++;
|
||||
}
|
||||
}
|
||||
vaddr = vmap(pages, npages, VM_MAP, pgprot);
|
||||
vfree(pages);
|
||||
|
||||
return vaddr;
|
||||
}
|
||||
|
||||
void ion_heap_unmap_kernel(struct ion_heap *heap,
|
||||
struct ion_buffer *buffer)
|
||||
{
|
||||
vunmap(buffer->vaddr);
|
||||
}
|
||||
|
||||
int ion_heap_map_user(struct ion_heap *heap, struct ion_buffer *buffer,
|
||||
struct vm_area_struct *vma)
|
||||
{
|
||||
struct sg_table *table = buffer->sg_table;
|
||||
unsigned long addr = vma->vm_start;
|
||||
unsigned long offset = vma->vm_pgoff * PAGE_SIZE;
|
||||
struct scatterlist *sg;
|
||||
int i;
|
||||
|
||||
for_each_sg(table->sgl, sg, table->nents, i) {
|
||||
struct page *page = sg_page(sg);
|
||||
unsigned long remainder = vma->vm_end - addr;
|
||||
unsigned long len = sg_dma_len(sg);
|
||||
|
||||
if (offset >= sg_dma_len(sg)) {
|
||||
offset -= sg_dma_len(sg);
|
||||
continue;
|
||||
} else if (offset) {
|
||||
page += offset / PAGE_SIZE;
|
||||
len = sg_dma_len(sg) - offset;
|
||||
offset = 0;
|
||||
}
|
||||
len = min(len, remainder);
|
||||
remap_pfn_range(vma, addr, page_to_pfn(page), len,
|
||||
vma->vm_page_prot);
|
||||
addr += len;
|
||||
if (addr >= vma->vm_end)
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ion_heap_buffer_zero(struct ion_buffer *buffer)
|
||||
{
|
||||
struct sg_table *table = buffer->sg_table;
|
||||
pgprot_t pgprot;
|
||||
struct scatterlist *sg;
|
||||
struct vm_struct *vm_struct;
|
||||
int i, j, ret = 0;
|
||||
|
||||
if (buffer->flags & ION_FLAG_CACHED)
|
||||
pgprot = PAGE_KERNEL;
|
||||
else
|
||||
pgprot = pgprot_writecombine(PAGE_KERNEL);
|
||||
|
||||
vm_struct = get_vm_area(PAGE_SIZE, VM_ALLOC);
|
||||
if (!vm_struct)
|
||||
return -ENOMEM;
|
||||
|
||||
for_each_sg(table->sgl, sg, table->nents, i) {
|
||||
struct page *page = sg_page(sg);
|
||||
unsigned long len = sg_dma_len(sg);
|
||||
|
||||
for (j = 0; j < len / PAGE_SIZE; j++) {
|
||||
struct page *sub_page = page + j;
|
||||
struct page **pages = &sub_page;
|
||||
ret = map_vm_area(vm_struct, pgprot, &pages);
|
||||
if (ret)
|
||||
goto end;
|
||||
memset(vm_struct->addr, 0, PAGE_SIZE);
|
||||
unmap_kernel_range((unsigned long)vm_struct->addr,
|
||||
PAGE_SIZE);
|
||||
}
|
||||
}
|
||||
end:
|
||||
free_vm_area(vm_struct);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ion_heap_free_page(struct ion_buffer *buffer, struct page *page,
|
||||
unsigned int order)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!ion_buffer_fault_user_mappings(buffer)) {
|
||||
__free_pages(page, order);
|
||||
return;
|
||||
}
|
||||
for (i = 0; i < (1 << order); i++)
|
||||
__free_page(page + i);
|
||||
}
|
||||
|
||||
void ion_heap_freelist_add(struct ion_heap *heap, struct ion_buffer * buffer)
|
||||
{
|
||||
rt_mutex_lock(&heap->lock);
|
||||
list_add(&buffer->list, &heap->free_list);
|
||||
heap->free_list_size += buffer->size;
|
||||
rt_mutex_unlock(&heap->lock);
|
||||
wake_up(&heap->waitqueue);
|
||||
}
|
||||
|
||||
size_t ion_heap_freelist_size(struct ion_heap *heap)
|
||||
{
|
||||
size_t size;
|
||||
|
||||
rt_mutex_lock(&heap->lock);
|
||||
size = heap->free_list_size;
|
||||
rt_mutex_unlock(&heap->lock);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t ion_heap_freelist_drain(struct ion_heap *heap, size_t size)
|
||||
{
|
||||
struct ion_buffer *buffer, *tmp;
|
||||
size_t total_drained = 0;
|
||||
|
||||
if (ion_heap_freelist_size(heap) == 0)
|
||||
return 0;
|
||||
|
||||
rt_mutex_lock(&heap->lock);
|
||||
if (size == 0)
|
||||
size = heap->free_list_size;
|
||||
|
||||
list_for_each_entry_safe(buffer, tmp, &heap->free_list, list) {
|
||||
if (total_drained >= size)
|
||||
break;
|
||||
list_del(&buffer->list);
|
||||
ion_buffer_destroy(buffer);
|
||||
heap->free_list_size -= buffer->size;
|
||||
total_drained += buffer->size;
|
||||
}
|
||||
rt_mutex_unlock(&heap->lock);
|
||||
|
||||
return total_drained;
|
||||
}
|
||||
|
||||
int ion_heap_deferred_free(void *data)
|
||||
{
|
||||
struct ion_heap *heap = data;
|
||||
|
||||
while (true) {
|
||||
struct ion_buffer *buffer;
|
||||
|
||||
wait_event_freezable(heap->waitqueue,
|
||||
ion_heap_freelist_size(heap) > 0);
|
||||
|
||||
rt_mutex_lock(&heap->lock);
|
||||
if (list_empty(&heap->free_list)) {
|
||||
rt_mutex_unlock(&heap->lock);
|
||||
continue;
|
||||
}
|
||||
buffer = list_first_entry(&heap->free_list, struct ion_buffer,
|
||||
list);
|
||||
list_del(&buffer->list);
|
||||
heap->free_list_size -= buffer->size;
|
||||
rt_mutex_unlock(&heap->lock);
|
||||
ion_buffer_destroy(buffer);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ion_heap_init_deferred_free(struct ion_heap *heap)
|
||||
{
|
||||
struct sched_param param = { .sched_priority = 0 };
|
||||
|
||||
INIT_LIST_HEAD(&heap->free_list);
|
||||
heap->free_list_size = 0;
|
||||
rt_mutex_init(&heap->lock);
|
||||
init_waitqueue_head(&heap->waitqueue);
|
||||
heap->task = kthread_run(ion_heap_deferred_free, heap,
|
||||
"%s", heap->name);
|
||||
sched_setscheduler(heap->task, SCHED_IDLE, ¶m);
|
||||
if (IS_ERR(heap->task)) {
|
||||
pr_err("%s: creating thread for deferred free failed\n",
|
||||
__func__);
|
||||
return PTR_RET(heap->task);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct ion_heap *ion_heap_create(struct ion_platform_heap *heap_data)
|
||||
{
|
||||
struct ion_heap *heap = NULL;
|
||||
|
||||
switch (heap_data->type) {
|
||||
case ION_HEAP_TYPE_SYSTEM_CONTIG:
|
||||
heap = ion_system_contig_heap_create(heap_data);
|
||||
break;
|
||||
case ION_HEAP_TYPE_SYSTEM:
|
||||
heap = ion_system_heap_create(heap_data);
|
||||
break;
|
||||
case ION_HEAP_TYPE_CARVEOUT:
|
||||
heap = ion_carveout_heap_create(heap_data);
|
||||
break;
|
||||
case ION_HEAP_TYPE_CHUNK:
|
||||
heap = ion_chunk_heap_create(heap_data);
|
||||
break;
|
||||
default:
|
||||
pr_err("%s: Invalid heap type %d\n", __func__,
|
||||
heap_data->type);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
if (IS_ERR_OR_NULL(heap)) {
|
||||
pr_err("%s: error creating heap %s type %d base %lu size %u\n",
|
||||
__func__, heap_data->name, heap_data->type,
|
||||
heap_data->base, heap_data->size);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
heap->name = heap_data->name;
|
||||
heap->id = heap_data->id;
|
||||
return heap;
|
||||
}
|
||||
|
||||
void ion_heap_destroy(struct ion_heap *heap)
|
||||
{
|
||||
if (!heap)
|
||||
return;
|
||||
|
||||
switch (heap->type) {
|
||||
case ION_HEAP_TYPE_SYSTEM_CONTIG:
|
||||
ion_system_contig_heap_destroy(heap);
|
||||
break;
|
||||
case ION_HEAP_TYPE_SYSTEM:
|
||||
ion_system_heap_destroy(heap);
|
||||
break;
|
||||
case ION_HEAP_TYPE_CARVEOUT:
|
||||
ion_carveout_heap_destroy(heap);
|
||||
break;
|
||||
case ION_HEAP_TYPE_CHUNK:
|
||||
ion_chunk_heap_destroy(heap);
|
||||
break;
|
||||
default:
|
||||
pr_err("%s: Invalid heap type %d\n", __func__,
|
||||
heap->type);
|
||||
}
|
||||
}
|
||||
200
drivers/gpu/ion/ion_page_pool.c
Normal file
200
drivers/gpu/ion/ion_page_pool.c
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* drivers/gpu/ion/ion_mem_pool.c
|
||||
*
|
||||
* Copyright (C) 2011 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/debugfs.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include "ion_priv.h"
|
||||
|
||||
struct ion_page_pool_item {
|
||||
struct page *page;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
static void *ion_page_pool_alloc_pages(struct ion_page_pool *pool)
|
||||
{
|
||||
struct page *page = alloc_pages(pool->gfp_mask, pool->order);
|
||||
|
||||
if (!page)
|
||||
return NULL;
|
||||
/* this is only being used to flush the page for dma,
|
||||
this api is not really suitable for calling from a driver
|
||||
but no better way to flush a page for dma exist at this time */
|
||||
arm_dma_ops.sync_single_for_device(NULL,
|
||||
pfn_to_dma(NULL, page_to_pfn(page)),
|
||||
PAGE_SIZE << pool->order,
|
||||
DMA_BIDIRECTIONAL);
|
||||
return page;
|
||||
}
|
||||
|
||||
static void ion_page_pool_free_pages(struct ion_page_pool *pool,
|
||||
struct page *page)
|
||||
{
|
||||
__free_pages(page, pool->order);
|
||||
}
|
||||
|
||||
static int ion_page_pool_add(struct ion_page_pool *pool, struct page *page)
|
||||
{
|
||||
struct ion_page_pool_item *item;
|
||||
|
||||
item = kmalloc(sizeof(struct ion_page_pool_item), GFP_KERNEL);
|
||||
if (!item)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_lock(&pool->mutex);
|
||||
item->page = page;
|
||||
if (PageHighMem(page)) {
|
||||
list_add_tail(&item->list, &pool->high_items);
|
||||
pool->high_count++;
|
||||
} else {
|
||||
list_add_tail(&item->list, &pool->low_items);
|
||||
pool->low_count++;
|
||||
}
|
||||
mutex_unlock(&pool->mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct page *ion_page_pool_remove(struct ion_page_pool *pool, bool high)
|
||||
{
|
||||
struct ion_page_pool_item *item;
|
||||
struct page *page;
|
||||
|
||||
if (high) {
|
||||
BUG_ON(!pool->high_count);
|
||||
item = list_first_entry(&pool->high_items,
|
||||
struct ion_page_pool_item, list);
|
||||
pool->high_count--;
|
||||
} else {
|
||||
BUG_ON(!pool->low_count);
|
||||
item = list_first_entry(&pool->low_items,
|
||||
struct ion_page_pool_item, list);
|
||||
pool->low_count--;
|
||||
}
|
||||
|
||||
list_del(&item->list);
|
||||
page = item->page;
|
||||
kfree(item);
|
||||
return page;
|
||||
}
|
||||
|
||||
void *ion_page_pool_alloc(struct ion_page_pool *pool)
|
||||
{
|
||||
struct page *page = NULL;
|
||||
|
||||
BUG_ON(!pool);
|
||||
|
||||
mutex_lock(&pool->mutex);
|
||||
if (pool->high_count)
|
||||
page = ion_page_pool_remove(pool, true);
|
||||
else if (pool->low_count)
|
||||
page = ion_page_pool_remove(pool, false);
|
||||
mutex_unlock(&pool->mutex);
|
||||
|
||||
if (!page)
|
||||
page = ion_page_pool_alloc_pages(pool);
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
void ion_page_pool_free(struct ion_page_pool *pool, struct page* page)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = ion_page_pool_add(pool, page);
|
||||
if (ret)
|
||||
ion_page_pool_free_pages(pool, page);
|
||||
}
|
||||
|
||||
static int ion_page_pool_total(struct ion_page_pool *pool, bool high)
|
||||
{
|
||||
int total = 0;
|
||||
|
||||
total += high ? (pool->high_count + pool->low_count) *
|
||||
(1 << pool->order) :
|
||||
pool->low_count * (1 << pool->order);
|
||||
return total;
|
||||
}
|
||||
|
||||
int ion_page_pool_shrink(struct ion_page_pool *pool, gfp_t gfp_mask,
|
||||
int nr_to_scan)
|
||||
{
|
||||
int nr_freed = 0;
|
||||
int i;
|
||||
bool high;
|
||||
|
||||
high = gfp_mask & __GFP_HIGHMEM;
|
||||
|
||||
if (nr_to_scan == 0)
|
||||
return ion_page_pool_total(pool, high);
|
||||
|
||||
for (i = 0; i < nr_to_scan; i++) {
|
||||
struct page *page;
|
||||
|
||||
mutex_lock(&pool->mutex);
|
||||
if (high && pool->high_count) {
|
||||
page = ion_page_pool_remove(pool, true);
|
||||
} else if (pool->low_count) {
|
||||
page = ion_page_pool_remove(pool, false);
|
||||
} else {
|
||||
mutex_unlock(&pool->mutex);
|
||||
break;
|
||||
}
|
||||
mutex_unlock(&pool->mutex);
|
||||
ion_page_pool_free_pages(pool, page);
|
||||
nr_freed += (1 << pool->order);
|
||||
}
|
||||
|
||||
return nr_freed;
|
||||
}
|
||||
|
||||
struct ion_page_pool *ion_page_pool_create(gfp_t gfp_mask, unsigned int order)
|
||||
{
|
||||
struct ion_page_pool *pool = kmalloc(sizeof(struct ion_page_pool),
|
||||
GFP_KERNEL);
|
||||
if (!pool)
|
||||
return NULL;
|
||||
pool->high_count = 0;
|
||||
pool->low_count = 0;
|
||||
INIT_LIST_HEAD(&pool->low_items);
|
||||
INIT_LIST_HEAD(&pool->high_items);
|
||||
pool->gfp_mask = gfp_mask;
|
||||
pool->order = order;
|
||||
mutex_init(&pool->mutex);
|
||||
plist_node_init(&pool->list, order);
|
||||
|
||||
return pool;
|
||||
}
|
||||
|
||||
void ion_page_pool_destroy(struct ion_page_pool *pool)
|
||||
{
|
||||
kfree(pool);
|
||||
}
|
||||
|
||||
static int __init ion_page_pool_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit ion_page_pool_exit(void)
|
||||
{
|
||||
}
|
||||
|
||||
module_init(ion_page_pool_init);
|
||||
module_exit(ion_page_pool_exit);
|
||||
341
drivers/gpu/ion/ion_priv.h
Normal file
341
drivers/gpu/ion/ion_priv.h
Normal file
|
|
@ -0,0 +1,341 @@
|
|||
/*
|
||||
* drivers/gpu/ion/ion_priv.h
|
||||
*
|
||||
* Copyright (C) 2011 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 _ION_PRIV_H
|
||||
#define _ION_PRIV_H
|
||||
|
||||
#include <linux/ion.h>
|
||||
#include <linux/kref.h>
|
||||
#include <linux/mm_types.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/rbtree.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/shrinker.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
struct ion_buffer *ion_handle_buffer(struct ion_handle *handle);
|
||||
|
||||
/**
|
||||
* struct ion_buffer - metadata for a particular buffer
|
||||
* @ref: refernce count
|
||||
* @node: node in the ion_device buffers tree
|
||||
* @dev: back pointer to the ion_device
|
||||
* @heap: back pointer to the heap the buffer came from
|
||||
* @flags: buffer specific flags
|
||||
* @size: size of the buffer
|
||||
* @priv_virt: private data to the buffer representable as
|
||||
* a void *
|
||||
* @priv_phys: private data to the buffer representable as
|
||||
* an ion_phys_addr_t (and someday a phys_addr_t)
|
||||
* @lock: protects the buffers cnt fields
|
||||
* @kmap_cnt: number of times the buffer is mapped to the kernel
|
||||
* @vaddr: the kenrel mapping if kmap_cnt is not zero
|
||||
* @dmap_cnt: number of times the buffer is mapped for dma
|
||||
* @sg_table: the sg table for the buffer if dmap_cnt is not zero
|
||||
* @dirty: bitmask representing which pages of this buffer have
|
||||
* been dirtied by the cpu and need cache maintenance
|
||||
* before dma
|
||||
* @vmas: list of vma's mapping this buffer
|
||||
* @handle_count: count of handles referencing this buffer
|
||||
* @task_comm: taskcomm of last client to reference this buffer in a
|
||||
* handle, used for debugging
|
||||
* @pid: pid of last client to reference this buffer in a
|
||||
* handle, used for debugging
|
||||
*/
|
||||
struct ion_buffer {
|
||||
struct kref ref;
|
||||
union {
|
||||
struct rb_node node;
|
||||
struct list_head list;
|
||||
};
|
||||
struct ion_device *dev;
|
||||
struct ion_heap *heap;
|
||||
unsigned long flags;
|
||||
size_t size;
|
||||
union {
|
||||
void *priv_virt;
|
||||
ion_phys_addr_t priv_phys;
|
||||
};
|
||||
struct mutex lock;
|
||||
int kmap_cnt;
|
||||
void *vaddr;
|
||||
int dmap_cnt;
|
||||
struct sg_table *sg_table;
|
||||
unsigned long *dirty;
|
||||
struct list_head vmas;
|
||||
/* used to track orphaned buffers */
|
||||
int handle_count;
|
||||
char task_comm[TASK_COMM_LEN];
|
||||
pid_t pid;
|
||||
};
|
||||
void ion_buffer_destroy(struct ion_buffer *buffer);
|
||||
|
||||
/**
|
||||
* struct ion_heap_ops - ops to operate on a given heap
|
||||
* @allocate: allocate memory
|
||||
* @free: free memory
|
||||
* @phys get physical address of a buffer (only define on
|
||||
* physically contiguous heaps)
|
||||
* @map_dma map the memory for dma to a scatterlist
|
||||
* @unmap_dma unmap the memory for dma
|
||||
* @map_kernel map memory to the kernel
|
||||
* @unmap_kernel unmap memory to the kernel
|
||||
* @map_user map memory to userspace
|
||||
*/
|
||||
struct ion_heap_ops {
|
||||
int (*allocate) (struct ion_heap *heap,
|
||||
struct ion_buffer *buffer, unsigned long len,
|
||||
unsigned long align, unsigned long flags);
|
||||
void (*free) (struct ion_buffer *buffer);
|
||||
int (*phys) (struct ion_heap *heap, struct ion_buffer *buffer,
|
||||
ion_phys_addr_t *addr, size_t *len);
|
||||
struct sg_table *(*map_dma) (struct ion_heap *heap,
|
||||
struct ion_buffer *buffer);
|
||||
void (*unmap_dma) (struct ion_heap *heap, struct ion_buffer *buffer);
|
||||
void * (*map_kernel) (struct ion_heap *heap, struct ion_buffer *buffer);
|
||||
void (*unmap_kernel) (struct ion_heap *heap, struct ion_buffer *buffer);
|
||||
int (*map_user) (struct ion_heap *mapper, struct ion_buffer *buffer,
|
||||
struct vm_area_struct *vma);
|
||||
};
|
||||
|
||||
/**
|
||||
* heap flags - flags between the heaps and core ion code
|
||||
*/
|
||||
#define ION_HEAP_FLAG_DEFER_FREE (1 << 0)
|
||||
|
||||
/**
|
||||
* struct ion_heap - represents a heap in the system
|
||||
* @node: rb node to put the heap on the device's tree of heaps
|
||||
* @dev: back pointer to the ion_device
|
||||
* @type: type of heap
|
||||
* @ops: ops struct as above
|
||||
* @flags: flags
|
||||
* @id: id of heap, also indicates priority of this heap when
|
||||
* allocating. These are specified by platform data and
|
||||
* MUST be unique
|
||||
* @name: used for debugging
|
||||
* @shrinker: a shrinker for the heap, if the heap caches system
|
||||
* memory, it must define a shrinker to return it on low
|
||||
* memory conditions, this includes system memory cached
|
||||
* in the deferred free lists for heaps that support it
|
||||
* @free_list: free list head if deferred free is used
|
||||
* @free_list_size size of the deferred free list in bytes
|
||||
* @lock: protects the free list
|
||||
* @waitqueue: queue to wait on from deferred free thread
|
||||
* @task: task struct of deferred free thread
|
||||
* @debug_show: called when heap debug file is read to add any
|
||||
* heap specific debug info to output
|
||||
*
|
||||
* Represents a pool of memory from which buffers can be made. In some
|
||||
* systems the only heap is regular system memory allocated via vmalloc.
|
||||
* On others, some blocks might require large physically contiguous buffers
|
||||
* that are allocated from a specially reserved heap.
|
||||
*/
|
||||
struct ion_heap {
|
||||
struct plist_node node;
|
||||
struct ion_device *dev;
|
||||
enum ion_heap_type type;
|
||||
struct ion_heap_ops *ops;
|
||||
unsigned long flags;
|
||||
unsigned int id;
|
||||
const char *name;
|
||||
struct shrinker shrinker;
|
||||
struct list_head free_list;
|
||||
size_t free_list_size;
|
||||
struct rt_mutex lock;
|
||||
wait_queue_head_t waitqueue;
|
||||
struct task_struct *task;
|
||||
int (*debug_show)(struct ion_heap *heap, struct seq_file *, void *);
|
||||
};
|
||||
|
||||
/**
|
||||
* ion_buffer_cached - this ion buffer is cached
|
||||
* @buffer: buffer
|
||||
*
|
||||
* indicates whether this ion buffer is cached
|
||||
*/
|
||||
bool ion_buffer_cached(struct ion_buffer *buffer);
|
||||
|
||||
/**
|
||||
* ion_buffer_fault_user_mappings - fault in user mappings of this buffer
|
||||
* @buffer: buffer
|
||||
*
|
||||
* indicates whether userspace mappings of this buffer will be faulted
|
||||
* in, this can affect how buffers are allocated from the heap.
|
||||
*/
|
||||
bool ion_buffer_fault_user_mappings(struct ion_buffer *buffer);
|
||||
|
||||
/**
|
||||
* ion_device_create - allocates and returns an ion device
|
||||
* @custom_ioctl: arch specific ioctl function if applicable
|
||||
*
|
||||
* returns a valid device or -PTR_ERR
|
||||
*/
|
||||
struct ion_device *ion_device_create(long (*custom_ioctl)
|
||||
(struct ion_client *client,
|
||||
unsigned int cmd,
|
||||
unsigned long arg));
|
||||
|
||||
/**
|
||||
* ion_device_destroy - free and device and it's resource
|
||||
* @dev: the device
|
||||
*/
|
||||
void ion_device_destroy(struct ion_device *dev);
|
||||
|
||||
/**
|
||||
* ion_device_add_heap - adds a heap to the ion device
|
||||
* @dev: the device
|
||||
* @heap: the heap to add
|
||||
*/
|
||||
void ion_device_add_heap(struct ion_device *dev, struct ion_heap *heap);
|
||||
|
||||
/**
|
||||
* some helpers for common operations on buffers using the sg_table
|
||||
* and vaddr fields
|
||||
*/
|
||||
void *ion_heap_map_kernel(struct ion_heap *, struct ion_buffer *);
|
||||
void ion_heap_unmap_kernel(struct ion_heap *, struct ion_buffer *);
|
||||
int ion_heap_map_user(struct ion_heap *, struct ion_buffer *,
|
||||
struct vm_area_struct *);
|
||||
int ion_heap_buffer_zero(struct ion_buffer *buffer);
|
||||
|
||||
/**
|
||||
* ion_heap_init_deferred_free -- initialize deferred free functionality
|
||||
* @heap: the heap
|
||||
*
|
||||
* If a heap sets the ION_HEAP_FLAG_DEFER_FREE flag this function will
|
||||
* be called to setup deferred frees. Calls to free the buffer will
|
||||
* return immediately and the actual free will occur some time later
|
||||
*/
|
||||
int ion_heap_init_deferred_free(struct ion_heap *heap);
|
||||
|
||||
/**
|
||||
* ion_heap_freelist_add - add a buffer to the deferred free list
|
||||
* @heap: the heap
|
||||
* @buffer: the buffer
|
||||
*
|
||||
* Adds an item to the deferred freelist.
|
||||
*/
|
||||
void ion_heap_freelist_add(struct ion_heap *heap, struct ion_buffer *buffer);
|
||||
|
||||
/**
|
||||
* ion_heap_freelist_drain - drain the deferred free list
|
||||
* @heap: the heap
|
||||
* @size: ammount of memory to drain in bytes
|
||||
*
|
||||
* Drains the indicated amount of memory from the deferred freelist immediately.
|
||||
* Returns the total amount freed. The total freed may be higher depending
|
||||
* on the size of the items in the list, or lower if there is insufficient
|
||||
* total memory on the freelist.
|
||||
*/
|
||||
size_t ion_heap_freelist_drain(struct ion_heap *heap, size_t size);
|
||||
|
||||
/**
|
||||
* ion_heap_freelist_size - returns the size of the freelist in bytes
|
||||
* @heap: the heap
|
||||
*/
|
||||
size_t ion_heap_freelist_size(struct ion_heap *heap);
|
||||
|
||||
|
||||
/**
|
||||
* functions for creating and destroying the built in ion heaps.
|
||||
* architectures can add their own custom architecture specific
|
||||
* heaps as appropriate.
|
||||
*/
|
||||
|
||||
struct ion_heap *ion_heap_create(struct ion_platform_heap *);
|
||||
void ion_heap_destroy(struct ion_heap *);
|
||||
struct ion_heap *ion_system_heap_create(struct ion_platform_heap *);
|
||||
void ion_system_heap_destroy(struct ion_heap *);
|
||||
|
||||
struct ion_heap *ion_system_contig_heap_create(struct ion_platform_heap *);
|
||||
void ion_system_contig_heap_destroy(struct ion_heap *);
|
||||
|
||||
struct ion_heap *ion_carveout_heap_create(struct ion_platform_heap *);
|
||||
void ion_carveout_heap_destroy(struct ion_heap *);
|
||||
|
||||
struct ion_heap *ion_chunk_heap_create(struct ion_platform_heap *);
|
||||
void ion_chunk_heap_destroy(struct ion_heap *);
|
||||
/**
|
||||
* kernel api to allocate/free from carveout -- used when carveout is
|
||||
* used to back an architecture specific custom heap
|
||||
*/
|
||||
ion_phys_addr_t ion_carveout_allocate(struct ion_heap *heap, unsigned long size,
|
||||
unsigned long align);
|
||||
void ion_carveout_free(struct ion_heap *heap, ion_phys_addr_t addr,
|
||||
unsigned long size);
|
||||
/**
|
||||
* The carveout heap returns physical addresses, since 0 may be a valid
|
||||
* physical address, this is used to indicate allocation failed
|
||||
*/
|
||||
#define ION_CARVEOUT_ALLOCATE_FAIL -1
|
||||
|
||||
/**
|
||||
* functions for creating and destroying a heap pool -- allows you
|
||||
* to keep a pool of pre allocated memory to use from your heap. Keeping
|
||||
* a pool of memory that is ready for dma, ie any cached mapping have been
|
||||
* invalidated from the cache, provides a significant peformance benefit on
|
||||
* many systems */
|
||||
|
||||
/**
|
||||
* struct ion_page_pool - pagepool struct
|
||||
* @high_count: number of highmem items in the pool
|
||||
* @low_count: number of lowmem items in the pool
|
||||
* @high_items: list of highmem items
|
||||
* @low_items: list of lowmem items
|
||||
* @shrinker: a shrinker for the items
|
||||
* @mutex: lock protecting this struct and especially the count
|
||||
* item list
|
||||
* @alloc: function to be used to allocate pageory when the pool
|
||||
* is empty
|
||||
* @free: function to be used to free pageory back to the system
|
||||
* when the shrinker fires
|
||||
* @gfp_mask: gfp_mask to use from alloc
|
||||
* @order: order of pages in the pool
|
||||
* @list: plist node for list of pools
|
||||
*
|
||||
* Allows you to keep a pool of pre allocated pages to use from your heap.
|
||||
* Keeping a pool of pages that is ready for dma, ie any cached mapping have
|
||||
* been invalidated from the cache, provides a significant peformance benefit
|
||||
* on many systems
|
||||
*/
|
||||
struct ion_page_pool {
|
||||
int high_count;
|
||||
int low_count;
|
||||
struct list_head high_items;
|
||||
struct list_head low_items;
|
||||
struct mutex mutex;
|
||||
gfp_t gfp_mask;
|
||||
unsigned int order;
|
||||
struct plist_node list;
|
||||
};
|
||||
|
||||
struct ion_page_pool *ion_page_pool_create(gfp_t gfp_mask, unsigned int order);
|
||||
void ion_page_pool_destroy(struct ion_page_pool *);
|
||||
void *ion_page_pool_alloc(struct ion_page_pool *);
|
||||
void ion_page_pool_free(struct ion_page_pool *, struct page *);
|
||||
|
||||
/** ion_page_pool_shrink - shrinks the size of the memory cached in the pool
|
||||
* @pool: the pool
|
||||
* @gfp_mask: the memory type to reclaim
|
||||
* @nr_to_scan: number of items to shrink in pages
|
||||
*
|
||||
* returns the number of items freed in pages
|
||||
*/
|
||||
int ion_page_pool_shrink(struct ion_page_pool *pool, gfp_t gfp_mask,
|
||||
int nr_to_scan);
|
||||
|
||||
#endif /* _ION_PRIV_H */
|
||||
467
drivers/gpu/ion/ion_system_heap.c
Normal file
467
drivers/gpu/ion/ion_system_heap.c
Normal file
|
|
@ -0,0 +1,467 @@
|
|||
/*
|
||||
* drivers/gpu/ion/ion_system_heap.c
|
||||
*
|
||||
* Copyright (C) 2011 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/page.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/highmem.h>
|
||||
#include <linux/ion.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/scatterlist.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include "ion_priv.h"
|
||||
|
||||
static unsigned int high_order_gfp_flags = (GFP_HIGHUSER | __GFP_ZERO |
|
||||
__GFP_NOWARN | __GFP_NORETRY) &
|
||||
~__GFP_WAIT;
|
||||
static unsigned int low_order_gfp_flags = (GFP_HIGHUSER | __GFP_ZERO |
|
||||
__GFP_NOWARN);
|
||||
static const unsigned int orders[] = {8, 4, 0};
|
||||
static const int num_orders = ARRAY_SIZE(orders);
|
||||
static int order_to_index(unsigned int order)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < num_orders; i++)
|
||||
if (order == orders[i])
|
||||
return i;
|
||||
BUG();
|
||||
return -1;
|
||||
}
|
||||
|
||||
static unsigned int order_to_size(int order)
|
||||
{
|
||||
return PAGE_SIZE << order;
|
||||
}
|
||||
|
||||
struct ion_system_heap {
|
||||
struct ion_heap heap;
|
||||
struct ion_page_pool **pools;
|
||||
};
|
||||
|
||||
struct page_info {
|
||||
struct page *page;
|
||||
unsigned int order;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
static struct page *alloc_buffer_page(struct ion_system_heap *heap,
|
||||
struct ion_buffer *buffer,
|
||||
unsigned long order)
|
||||
{
|
||||
bool cached = ion_buffer_cached(buffer);
|
||||
bool split_pages = ion_buffer_fault_user_mappings(buffer);
|
||||
struct ion_page_pool *pool = heap->pools[order_to_index(order)];
|
||||
struct page *page;
|
||||
|
||||
if (!cached) {
|
||||
page = ion_page_pool_alloc(pool);
|
||||
} else {
|
||||
gfp_t gfp_flags = low_order_gfp_flags;
|
||||
|
||||
if (order > 4)
|
||||
gfp_flags = high_order_gfp_flags;
|
||||
page = alloc_pages(gfp_flags, order);
|
||||
if (!page)
|
||||
return 0;
|
||||
arm_dma_ops.sync_single_for_device(NULL,
|
||||
pfn_to_dma(NULL, page_to_pfn(page)),
|
||||
PAGE_SIZE << order, DMA_BIDIRECTIONAL);
|
||||
}
|
||||
if (!page)
|
||||
return 0;
|
||||
|
||||
if (split_pages)
|
||||
split_page(page, order);
|
||||
return page;
|
||||
}
|
||||
|
||||
static void free_buffer_page(struct ion_system_heap *heap,
|
||||
struct ion_buffer *buffer, struct page *page,
|
||||
unsigned int order)
|
||||
{
|
||||
bool cached = ion_buffer_cached(buffer);
|
||||
bool split_pages = ion_buffer_fault_user_mappings(buffer);
|
||||
int i;
|
||||
|
||||
if (!cached) {
|
||||
struct ion_page_pool *pool = heap->pools[order_to_index(order)];
|
||||
ion_page_pool_free(pool, page);
|
||||
} else if (split_pages) {
|
||||
for (i = 0; i < (1 << order); i++)
|
||||
__free_page(page + i);
|
||||
} else {
|
||||
__free_pages(page, order);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static struct page_info *alloc_largest_available(struct ion_system_heap *heap,
|
||||
struct ion_buffer *buffer,
|
||||
unsigned long size,
|
||||
unsigned int max_order)
|
||||
{
|
||||
struct page *page;
|
||||
struct page_info *info;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num_orders; i++) {
|
||||
if (size < order_to_size(orders[i]))
|
||||
continue;
|
||||
if (max_order < orders[i])
|
||||
continue;
|
||||
|
||||
page = alloc_buffer_page(heap, buffer, orders[i]);
|
||||
if (!page)
|
||||
continue;
|
||||
|
||||
info = kmalloc(sizeof(struct page_info), GFP_KERNEL);
|
||||
info->page = page;
|
||||
info->order = orders[i];
|
||||
return info;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int ion_system_heap_allocate(struct ion_heap *heap,
|
||||
struct ion_buffer *buffer,
|
||||
unsigned long size, unsigned long align,
|
||||
unsigned long flags)
|
||||
{
|
||||
struct ion_system_heap *sys_heap = container_of(heap,
|
||||
struct ion_system_heap,
|
||||
heap);
|
||||
struct sg_table *table;
|
||||
struct scatterlist *sg;
|
||||
int ret;
|
||||
struct list_head pages;
|
||||
struct page_info *info, *tmp_info;
|
||||
int i = 0;
|
||||
long size_remaining = PAGE_ALIGN(size);
|
||||
unsigned int max_order = orders[0];
|
||||
bool split_pages = ion_buffer_fault_user_mappings(buffer);
|
||||
|
||||
INIT_LIST_HEAD(&pages);
|
||||
while (size_remaining > 0) {
|
||||
info = alloc_largest_available(sys_heap, buffer, size_remaining, max_order);
|
||||
if (!info)
|
||||
goto err;
|
||||
list_add_tail(&info->list, &pages);
|
||||
size_remaining -= (1 << info->order) * PAGE_SIZE;
|
||||
max_order = info->order;
|
||||
i++;
|
||||
}
|
||||
|
||||
table = kmalloc(sizeof(struct sg_table), GFP_KERNEL);
|
||||
if (!table)
|
||||
goto err;
|
||||
|
||||
if (split_pages)
|
||||
ret = sg_alloc_table(table, PAGE_ALIGN(size) / PAGE_SIZE,
|
||||
GFP_KERNEL);
|
||||
else
|
||||
ret = sg_alloc_table(table, i, GFP_KERNEL);
|
||||
|
||||
if (ret)
|
||||
goto err1;
|
||||
|
||||
sg = table->sgl;
|
||||
list_for_each_entry_safe(info, tmp_info, &pages, list) {
|
||||
struct page *page = info->page;
|
||||
if (split_pages) {
|
||||
for (i = 0; i < (1 << info->order); i++) {
|
||||
sg_set_page(sg, page + i, PAGE_SIZE, 0);
|
||||
sg = sg_next(sg);
|
||||
}
|
||||
} else {
|
||||
sg_set_page(sg, page, (1 << info->order) * PAGE_SIZE,
|
||||
0);
|
||||
sg = sg_next(sg);
|
||||
}
|
||||
list_del(&info->list);
|
||||
kfree(info);
|
||||
}
|
||||
|
||||
buffer->priv_virt = table;
|
||||
return 0;
|
||||
err1:
|
||||
kfree(table);
|
||||
err:
|
||||
list_for_each_entry(info, &pages, list) {
|
||||
free_buffer_page(sys_heap, buffer, info->page, info->order);
|
||||
kfree(info);
|
||||
}
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
void ion_system_heap_free(struct ion_buffer *buffer)
|
||||
{
|
||||
struct ion_heap *heap = buffer->heap;
|
||||
struct ion_system_heap *sys_heap = container_of(heap,
|
||||
struct ion_system_heap,
|
||||
heap);
|
||||
struct sg_table *table = buffer->sg_table;
|
||||
bool cached = ion_buffer_cached(buffer);
|
||||
struct scatterlist *sg;
|
||||
LIST_HEAD(pages);
|
||||
int i;
|
||||
|
||||
/* uncached pages come from the page pools, zero them before returning
|
||||
for security purposes (other allocations are zerod at alloc time */
|
||||
if (!cached)
|
||||
ion_heap_buffer_zero(buffer);
|
||||
|
||||
for_each_sg(table->sgl, sg, table->nents, i)
|
||||
free_buffer_page(sys_heap, buffer, sg_page(sg),
|
||||
get_order(sg_dma_len(sg)));
|
||||
sg_free_table(table);
|
||||
kfree(table);
|
||||
}
|
||||
|
||||
struct sg_table *ion_system_heap_map_dma(struct ion_heap *heap,
|
||||
struct ion_buffer *buffer)
|
||||
{
|
||||
return buffer->priv_virt;
|
||||
}
|
||||
|
||||
void ion_system_heap_unmap_dma(struct ion_heap *heap,
|
||||
struct ion_buffer *buffer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
static struct ion_heap_ops system_heap_ops = {
|
||||
.allocate = ion_system_heap_allocate,
|
||||
.free = ion_system_heap_free,
|
||||
.map_dma = ion_system_heap_map_dma,
|
||||
.unmap_dma = ion_system_heap_unmap_dma,
|
||||
.map_kernel = ion_heap_map_kernel,
|
||||
.unmap_kernel = ion_heap_unmap_kernel,
|
||||
.map_user = ion_heap_map_user,
|
||||
};
|
||||
|
||||
static int ion_system_heap_shrink(struct shrinker *shrinker,
|
||||
struct shrink_control *sc) {
|
||||
|
||||
struct ion_heap *heap = container_of(shrinker, struct ion_heap,
|
||||
shrinker);
|
||||
struct ion_system_heap *sys_heap = container_of(heap,
|
||||
struct ion_system_heap,
|
||||
heap);
|
||||
int nr_total = 0;
|
||||
int nr_freed = 0;
|
||||
int i;
|
||||
|
||||
if (sc->nr_to_scan == 0)
|
||||
goto end;
|
||||
|
||||
/* shrink the free list first, no point in zeroing the memory if
|
||||
we're just going to reclaim it */
|
||||
nr_freed += ion_heap_freelist_drain(heap, sc->nr_to_scan * PAGE_SIZE) /
|
||||
PAGE_SIZE;
|
||||
|
||||
if (nr_freed >= sc->nr_to_scan)
|
||||
goto end;
|
||||
|
||||
for (i = 0; i < num_orders; i++) {
|
||||
struct ion_page_pool *pool = sys_heap->pools[i];
|
||||
|
||||
nr_freed += ion_page_pool_shrink(pool, sc->gfp_mask,
|
||||
sc->nr_to_scan);
|
||||
if (nr_freed >= sc->nr_to_scan)
|
||||
break;
|
||||
}
|
||||
|
||||
end:
|
||||
/* total number of items is whatever the page pools are holding
|
||||
plus whatever's in the freelist */
|
||||
for (i = 0; i < num_orders; i++) {
|
||||
struct ion_page_pool *pool = sys_heap->pools[i];
|
||||
nr_total += ion_page_pool_shrink(pool, sc->gfp_mask, 0);
|
||||
}
|
||||
nr_total += ion_heap_freelist_size(heap) / PAGE_SIZE;
|
||||
return nr_total;
|
||||
|
||||
}
|
||||
|
||||
static int ion_system_heap_debug_show(struct ion_heap *heap, struct seq_file *s,
|
||||
void *unused)
|
||||
{
|
||||
|
||||
struct ion_system_heap *sys_heap = container_of(heap,
|
||||
struct ion_system_heap,
|
||||
heap);
|
||||
int i;
|
||||
for (i = 0; i < num_orders; i++) {
|
||||
struct ion_page_pool *pool = sys_heap->pools[i];
|
||||
seq_printf(s, "%d order %u highmem pages in pool = %lu total\n",
|
||||
pool->high_count, pool->order,
|
||||
(1 << pool->order) * PAGE_SIZE * pool->high_count);
|
||||
seq_printf(s, "%d order %u lowmem pages in pool = %lu total\n",
|
||||
pool->low_count, pool->order,
|
||||
(1 << pool->order) * PAGE_SIZE * pool->low_count);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct ion_heap *ion_system_heap_create(struct ion_platform_heap *unused)
|
||||
{
|
||||
struct ion_system_heap *heap;
|
||||
int i;
|
||||
|
||||
heap = kzalloc(sizeof(struct ion_system_heap), GFP_KERNEL);
|
||||
if (!heap)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
heap->heap.ops = &system_heap_ops;
|
||||
heap->heap.type = ION_HEAP_TYPE_SYSTEM;
|
||||
heap->heap.flags = ION_HEAP_FLAG_DEFER_FREE;
|
||||
heap->pools = kzalloc(sizeof(struct ion_page_pool *) * num_orders,
|
||||
GFP_KERNEL);
|
||||
if (!heap->pools)
|
||||
goto err_alloc_pools;
|
||||
for (i = 0; i < num_orders; i++) {
|
||||
struct ion_page_pool *pool;
|
||||
gfp_t gfp_flags = low_order_gfp_flags;
|
||||
|
||||
if (orders[i] > 4)
|
||||
gfp_flags = high_order_gfp_flags;
|
||||
pool = ion_page_pool_create(gfp_flags, orders[i]);
|
||||
if (!pool)
|
||||
goto err_create_pool;
|
||||
heap->pools[i] = pool;
|
||||
}
|
||||
|
||||
heap->heap.shrinker.shrink = ion_system_heap_shrink;
|
||||
heap->heap.shrinker.seeks = DEFAULT_SEEKS;
|
||||
heap->heap.shrinker.batch = 0;
|
||||
register_shrinker(&heap->heap.shrinker);
|
||||
heap->heap.debug_show = ion_system_heap_debug_show;
|
||||
return &heap->heap;
|
||||
err_create_pool:
|
||||
for (i = 0; i < num_orders; i++)
|
||||
if (heap->pools[i])
|
||||
ion_page_pool_destroy(heap->pools[i]);
|
||||
kfree(heap->pools);
|
||||
err_alloc_pools:
|
||||
kfree(heap);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
void ion_system_heap_destroy(struct ion_heap *heap)
|
||||
{
|
||||
struct ion_system_heap *sys_heap = container_of(heap,
|
||||
struct ion_system_heap,
|
||||
heap);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num_orders; i++)
|
||||
ion_page_pool_destroy(sys_heap->pools[i]);
|
||||
kfree(sys_heap->pools);
|
||||
kfree(sys_heap);
|
||||
}
|
||||
|
||||
static int ion_system_contig_heap_allocate(struct ion_heap *heap,
|
||||
struct ion_buffer *buffer,
|
||||
unsigned long len,
|
||||
unsigned long align,
|
||||
unsigned long flags)
|
||||
{
|
||||
buffer->priv_virt = kzalloc(len, GFP_KERNEL);
|
||||
if (!buffer->priv_virt)
|
||||
return -ENOMEM;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ion_system_contig_heap_free(struct ion_buffer *buffer)
|
||||
{
|
||||
kfree(buffer->priv_virt);
|
||||
}
|
||||
|
||||
static int ion_system_contig_heap_phys(struct ion_heap *heap,
|
||||
struct ion_buffer *buffer,
|
||||
ion_phys_addr_t *addr, size_t *len)
|
||||
{
|
||||
*addr = virt_to_phys(buffer->priv_virt);
|
||||
*len = buffer->size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct sg_table *ion_system_contig_heap_map_dma(struct ion_heap *heap,
|
||||
struct ion_buffer *buffer)
|
||||
{
|
||||
struct sg_table *table;
|
||||
int ret;
|
||||
|
||||
table = kzalloc(sizeof(struct sg_table), GFP_KERNEL);
|
||||
if (!table)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
ret = sg_alloc_table(table, 1, GFP_KERNEL);
|
||||
if (ret) {
|
||||
kfree(table);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
sg_set_page(table->sgl, virt_to_page(buffer->priv_virt), buffer->size,
|
||||
0);
|
||||
return table;
|
||||
}
|
||||
|
||||
void ion_system_contig_heap_unmap_dma(struct ion_heap *heap,
|
||||
struct ion_buffer *buffer)
|
||||
{
|
||||
sg_free_table(buffer->sg_table);
|
||||
kfree(buffer->sg_table);
|
||||
}
|
||||
|
||||
int ion_system_contig_heap_map_user(struct ion_heap *heap,
|
||||
struct ion_buffer *buffer,
|
||||
struct vm_area_struct *vma)
|
||||
{
|
||||
unsigned long pfn = __phys_to_pfn(virt_to_phys(buffer->priv_virt));
|
||||
return remap_pfn_range(vma, vma->vm_start, pfn + vma->vm_pgoff,
|
||||
vma->vm_end - vma->vm_start,
|
||||
vma->vm_page_prot);
|
||||
|
||||
}
|
||||
|
||||
static struct ion_heap_ops kmalloc_ops = {
|
||||
.allocate = ion_system_contig_heap_allocate,
|
||||
.free = ion_system_contig_heap_free,
|
||||
.phys = ion_system_contig_heap_phys,
|
||||
.map_dma = ion_system_contig_heap_map_dma,
|
||||
.unmap_dma = ion_system_contig_heap_unmap_dma,
|
||||
.map_kernel = ion_heap_map_kernel,
|
||||
.unmap_kernel = ion_heap_unmap_kernel,
|
||||
.map_user = ion_system_contig_heap_map_user,
|
||||
};
|
||||
|
||||
struct ion_heap *ion_system_contig_heap_create(struct ion_platform_heap *unused)
|
||||
{
|
||||
struct ion_heap *heap;
|
||||
|
||||
heap = kzalloc(sizeof(struct ion_heap), GFP_KERNEL);
|
||||
if (!heap)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
heap->ops = &kmalloc_ops;
|
||||
heap->type = ION_HEAP_TYPE_SYSTEM_CONTIG;
|
||||
return heap;
|
||||
}
|
||||
|
||||
void ion_system_contig_heap_destroy(struct ion_heap *heap)
|
||||
{
|
||||
kfree(heap);
|
||||
}
|
||||
|
||||
114
drivers/gpu/ion/ion_system_mapper.c
Normal file
114
drivers/gpu/ion/ion_system_mapper.c
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* drivers/gpu/ion/ion_system_mapper.c
|
||||
*
|
||||
* Copyright (C) 2011 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/err.h>
|
||||
#include <linux/ion.h>
|
||||
#include <linux/memory.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include "ion_priv.h"
|
||||
/*
|
||||
* This mapper is valid for any heap that allocates memory that already has
|
||||
* a kernel mapping, this includes vmalloc'd memory, kmalloc'd memory,
|
||||
* pages obtained via io_remap, etc.
|
||||
*/
|
||||
static void *ion_kernel_mapper_map(struct ion_mapper *mapper,
|
||||
struct ion_buffer *buffer,
|
||||
struct ion_mapping **mapping)
|
||||
{
|
||||
if (!((1 << buffer->heap->type) & mapper->heap_mask)) {
|
||||
pr_err("%s: attempting to map an unsupported heap\n", __func__);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
/* XXX REVISIT ME!!! */
|
||||
*((unsigned long *)mapping) = (unsigned long)buffer->priv;
|
||||
return buffer->priv;
|
||||
}
|
||||
|
||||
static void ion_kernel_mapper_unmap(struct ion_mapper *mapper,
|
||||
struct ion_buffer *buffer,
|
||||
struct ion_mapping *mapping)
|
||||
{
|
||||
if (!((1 << buffer->heap->type) & mapper->heap_mask))
|
||||
pr_err("%s: attempting to unmap an unsupported heap\n",
|
||||
__func__);
|
||||
}
|
||||
|
||||
static void *ion_kernel_mapper_map_kernel(struct ion_mapper *mapper,
|
||||
struct ion_buffer *buffer,
|
||||
struct ion_mapping *mapping)
|
||||
{
|
||||
if (!((1 << buffer->heap->type) & mapper->heap_mask)) {
|
||||
pr_err("%s: attempting to unmap an unsupported heap\n",
|
||||
__func__);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
return buffer->priv;
|
||||
}
|
||||
|
||||
static int ion_kernel_mapper_map_user(struct ion_mapper *mapper,
|
||||
struct ion_buffer *buffer,
|
||||
struct vm_area_struct *vma,
|
||||
struct ion_mapping *mapping)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (buffer->heap->type) {
|
||||
case ION_HEAP_KMALLOC:
|
||||
{
|
||||
unsigned long pfn = __phys_to_pfn(virt_to_phys(buffer->priv));
|
||||
ret = remap_pfn_range(vma, vma->vm_start, pfn + vma->vm_pgoff,
|
||||
vma->vm_end - vma->vm_start,
|
||||
vma->vm_page_prot);
|
||||
break;
|
||||
}
|
||||
case ION_HEAP_VMALLOC:
|
||||
ret = remap_vmalloc_range(vma, buffer->priv, vma->vm_pgoff);
|
||||
break;
|
||||
default:
|
||||
pr_err("%s: attempting to map unsupported heap to userspace\n",
|
||||
__func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct ion_mapper_ops ops = {
|
||||
.map = ion_kernel_mapper_map,
|
||||
.map_kernel = ion_kernel_mapper_map_kernel,
|
||||
.map_user = ion_kernel_mapper_map_user,
|
||||
.unmap = ion_kernel_mapper_unmap,
|
||||
};
|
||||
|
||||
struct ion_mapper *ion_system_mapper_create(void)
|
||||
{
|
||||
struct ion_mapper *mapper;
|
||||
mapper = kzalloc(sizeof(struct ion_mapper), GFP_KERNEL);
|
||||
if (!mapper)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
mapper->type = ION_SYSTEM_MAPPER;
|
||||
mapper->ops = &ops;
|
||||
mapper->heap_mask = (1 << ION_HEAP_VMALLOC) | (1 << ION_HEAP_KMALLOC);
|
||||
return mapper;
|
||||
}
|
||||
|
||||
void ion_system_mapper_destroy(struct ion_mapper *mapper)
|
||||
{
|
||||
kfree(mapper);
|
||||
}
|
||||
|
||||
1
drivers/gpu/ion/tegra/Makefile
Normal file
1
drivers/gpu/ion/tegra/Makefile
Normal file
|
|
@ -0,0 +1 @@
|
|||
obj-y += tegra_ion.o
|
||||
96
drivers/gpu/ion/tegra/tegra_ion.c
Normal file
96
drivers/gpu/ion/tegra/tegra_ion.c
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* drivers/gpu/tegra/tegra_ion.c
|
||||
*
|
||||
* Copyright (C) 2011 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/err.h>
|
||||
#include <linux/ion.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include "../ion_priv.h"
|
||||
|
||||
struct ion_device *idev;
|
||||
struct ion_mapper *tegra_user_mapper;
|
||||
int num_heaps;
|
||||
struct ion_heap **heaps;
|
||||
|
||||
int tegra_ion_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct ion_platform_data *pdata = pdev->dev.platform_data;
|
||||
int err;
|
||||
int i;
|
||||
|
||||
num_heaps = pdata->nr;
|
||||
|
||||
heaps = kzalloc(sizeof(struct ion_heap *) * pdata->nr, GFP_KERNEL);
|
||||
|
||||
idev = ion_device_create(NULL);
|
||||
if (IS_ERR_OR_NULL(idev)) {
|
||||
kfree(heaps);
|
||||
return PTR_ERR(idev);
|
||||
}
|
||||
|
||||
/* create the heaps as specified in the board file */
|
||||
for (i = 0; i < num_heaps; i++) {
|
||||
struct ion_platform_heap *heap_data = &pdata->heaps[i];
|
||||
|
||||
heaps[i] = ion_heap_create(heap_data);
|
||||
if (IS_ERR_OR_NULL(heaps[i])) {
|
||||
err = PTR_ERR(heaps[i]);
|
||||
goto err;
|
||||
}
|
||||
ion_device_add_heap(idev, heaps[i]);
|
||||
}
|
||||
platform_set_drvdata(pdev, idev);
|
||||
return 0;
|
||||
err:
|
||||
for (i = 0; i < num_heaps; i++) {
|
||||
if (heaps[i])
|
||||
ion_heap_destroy(heaps[i]);
|
||||
}
|
||||
kfree(heaps);
|
||||
return err;
|
||||
}
|
||||
|
||||
int tegra_ion_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct ion_device *idev = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
ion_device_destroy(idev);
|
||||
for (i = 0; i < num_heaps; i++)
|
||||
ion_heap_destroy(heaps[i]);
|
||||
kfree(heaps);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver ion_driver = {
|
||||
.probe = tegra_ion_probe,
|
||||
.remove = tegra_ion_remove,
|
||||
.driver = { .name = "ion-tegra" }
|
||||
};
|
||||
|
||||
static int __init ion_init(void)
|
||||
{
|
||||
return platform_driver_register(&ion_driver);
|
||||
}
|
||||
|
||||
static void __exit ion_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&ion_driver);
|
||||
}
|
||||
|
||||
module_init(ion_init);
|
||||
module_exit(ion_exit);
|
||||
|
||||
|
|
@ -1321,8 +1321,9 @@ int hidinput_connect(struct hid_device *hid, unsigned int force)
|
|||
* UGCI) cram a lot of unrelated inputs into the
|
||||
* same interface. */
|
||||
hidinput->report = report;
|
||||
if (drv->input_configured)
|
||||
drv->input_configured(hid, hidinput);
|
||||
if (drv->input_configured &&
|
||||
drv->input_configured(hid, hidinput))
|
||||
goto out_cleanup;
|
||||
if (input_register_device(hidinput->input))
|
||||
goto out_cleanup;
|
||||
hidinput = NULL;
|
||||
|
|
@ -1343,8 +1344,9 @@ int hidinput_connect(struct hid_device *hid, unsigned int force)
|
|||
}
|
||||
|
||||
if (hidinput) {
|
||||
if (drv->input_configured)
|
||||
drv->input_configured(hid, hidinput);
|
||||
if (drv->input_configured &&
|
||||
drv->input_configured(hid, hidinput))
|
||||
goto out_cleanup;
|
||||
if (input_register_device(hidinput->input))
|
||||
goto out_cleanup;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -445,6 +445,16 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
|
|||
(usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON)
|
||||
td->mt_flags |= INPUT_MT_POINTER;
|
||||
|
||||
/* 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];
|
||||
|
||||
|
|
@ -770,12 +780,13 @@ static void mt_touch_report(struct hid_device *hid, struct hid_report *report)
|
|||
mt_sync_frame(td, report->field[0]->hidinput->input);
|
||||
}
|
||||
|
||||
static void mt_touch_input_configured(struct hid_device *hdev,
|
||||
static int mt_touch_input_configured(struct hid_device *hdev,
|
||||
struct hid_input *hi)
|
||||
{
|
||||
struct mt_device *td = hid_get_drvdata(hdev);
|
||||
struct mt_class *cls = &td->mtclass;
|
||||
struct input_dev *input = hi->input;
|
||||
int ret;
|
||||
|
||||
if (!td->maxcontacts)
|
||||
td->maxcontacts = MT_DEFAULT_MAXCONTACT;
|
||||
|
|
@ -790,9 +801,12 @@ static void mt_touch_input_configured(struct hid_device *hdev,
|
|||
if (cls->quirks & MT_QUIRK_NOT_SEEN_MEANS_UP)
|
||||
td->mt_flags |= INPUT_MT_DROP_UNUSED;
|
||||
|
||||
input_mt_init_slots(input, td->maxcontacts, td->mt_flags);
|
||||
ret = input_mt_init_slots(input, td->maxcontacts, td->mt_flags);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
td->mt_flags = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
|
||||
|
|
@ -925,19 +939,21 @@ static void mt_post_parse(struct mt_device *td)
|
|||
cls->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE;
|
||||
}
|
||||
|
||||
static void mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
|
||||
static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
|
||||
{
|
||||
struct mt_device *td = hid_get_drvdata(hdev);
|
||||
char *name = kstrdup(hdev->name, GFP_KERNEL);
|
||||
int ret = 0;
|
||||
|
||||
if (name)
|
||||
hi->input->name = name;
|
||||
|
||||
if (hi->report->id == td->mt_report_id)
|
||||
mt_touch_input_configured(hdev, hi);
|
||||
ret = mt_touch_input_configured(hdev, hi);
|
||||
|
||||
if (hi->report->id == td->pen_report_id)
|
||||
mt_pen_input_configured(hdev, hi);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
*/
|
||||
struct iio_event_interface {
|
||||
wait_queue_head_t wait;
|
||||
struct mutex read_lock;
|
||||
DECLARE_KFIFO(det_events, struct iio_event_data, 16);
|
||||
|
||||
struct list_head dev_attr_list;
|
||||
|
|
@ -97,14 +98,16 @@ static ssize_t iio_event_chrdev_read(struct file *filep,
|
|||
if (count < sizeof(struct iio_event_data))
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irq(&ev_int->wait.lock);
|
||||
if (mutex_lock_interruptible(&ev_int->read_lock))
|
||||
return -ERESTARTSYS;
|
||||
|
||||
if (kfifo_is_empty(&ev_int->det_events)) {
|
||||
if (filep->f_flags & O_NONBLOCK) {
|
||||
ret = -EAGAIN;
|
||||
goto error_unlock;
|
||||
}
|
||||
/* Blocking on device; waiting for something to be there */
|
||||
ret = wait_event_interruptible_locked_irq(ev_int->wait,
|
||||
ret = wait_event_interruptible(ev_int->wait,
|
||||
!kfifo_is_empty(&ev_int->det_events));
|
||||
if (ret)
|
||||
goto error_unlock;
|
||||
|
|
@ -114,7 +117,7 @@ static ssize_t iio_event_chrdev_read(struct file *filep,
|
|||
ret = kfifo_to_user(&ev_int->det_events, buf, count, &copied);
|
||||
|
||||
error_unlock:
|
||||
spin_unlock_irq(&ev_int->wait.lock);
|
||||
mutex_unlock(&ev_int->read_lock);
|
||||
|
||||
return ret ? ret : copied;
|
||||
}
|
||||
|
|
@ -371,6 +374,7 @@ static void iio_setup_ev_int(struct iio_event_interface *ev_int)
|
|||
{
|
||||
INIT_KFIFO(ev_int->det_events);
|
||||
init_waitqueue_head(&ev_int->wait);
|
||||
mutex_init(&ev_int->read_lock);
|
||||
}
|
||||
|
||||
static const char *iio_event_group_name = "events";
|
||||
|
|
@ -434,6 +438,7 @@ int iio_device_register_eventset(struct iio_dev *indio_dev)
|
|||
|
||||
error_free_setup_event_lines:
|
||||
__iio_remove_event_config_attrs(indio_dev);
|
||||
mutex_destroy(&indio_dev->event_interface->read_lock);
|
||||
kfree(indio_dev->event_interface);
|
||||
error_ret:
|
||||
|
||||
|
|
@ -446,5 +451,6 @@ void iio_device_unregister_eventset(struct iio_dev *indio_dev)
|
|||
return;
|
||||
__iio_remove_event_config_attrs(indio_dev);
|
||||
kfree(indio_dev->event_interface->group.attrs);
|
||||
mutex_destroy(&indio_dev->event_interface->read_lock);
|
||||
kfree(indio_dev->event_interface);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,6 +174,15 @@ config INPUT_APMPOWER
|
|||
To compile this driver as a module, choose M here: the
|
||||
module will be called apm-power.
|
||||
|
||||
config INPUT_KEYRESET
|
||||
tristate "Reset key"
|
||||
depends on INPUT
|
||||
---help---
|
||||
Say Y here if you want to reboot when some keys are pressed;
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called keyreset.
|
||||
|
||||
comment "Input Device Drivers"
|
||||
|
||||
source "drivers/input/keyboard/Kconfig"
|
||||
|
|
|
|||
|
|
@ -25,3 +25,4 @@ obj-$(CONFIG_INPUT_TOUCHSCREEN) += touchscreen/
|
|||
obj-$(CONFIG_INPUT_MISC) += misc/
|
||||
|
||||
obj-$(CONFIG_INPUT_APMPOWER) += apm-power.o
|
||||
obj-$(CONFIG_INPUT_KEYRESET) += keyreset.o
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
#include <linux/major.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/wakelock.h>
|
||||
#include "input-compat.h"
|
||||
|
||||
struct evdev {
|
||||
|
|
@ -44,6 +45,9 @@ struct evdev_client {
|
|||
unsigned int tail;
|
||||
unsigned int packet_head; /* [future] position of the first element of next packet */
|
||||
spinlock_t buffer_lock; /* protects access to buffer, head and tail */
|
||||
struct wake_lock wake_lock;
|
||||
bool use_wake_lock;
|
||||
char name[28];
|
||||
struct fasync_struct *fasync;
|
||||
struct evdev *evdev;
|
||||
struct list_head node;
|
||||
|
|
@ -71,10 +75,14 @@ static void __pass_event(struct evdev_client *client,
|
|||
client->buffer[client->tail].value = 0;
|
||||
|
||||
client->packet_head = client->tail;
|
||||
if (client->use_wake_lock)
|
||||
wake_unlock(&client->wake_lock);
|
||||
}
|
||||
|
||||
if (event->type == EV_SYN && event->code == SYN_REPORT) {
|
||||
client->packet_head = client->head;
|
||||
if (client->use_wake_lock)
|
||||
wake_lock(&client->wake_lock);
|
||||
kill_fasync(&client->fasync, SIGIO, POLL_IN);
|
||||
}
|
||||
}
|
||||
|
|
@ -289,6 +297,8 @@ static int evdev_release(struct inode *inode, struct file *file)
|
|||
mutex_unlock(&evdev->mutex);
|
||||
|
||||
evdev_detach_client(evdev, client);
|
||||
if (client->use_wake_lock)
|
||||
wake_lock_destroy(&client->wake_lock);
|
||||
kfree(client);
|
||||
|
||||
evdev_close_device(evdev);
|
||||
|
|
@ -320,6 +330,8 @@ static int evdev_open(struct inode *inode, struct file *file)
|
|||
|
||||
client->bufsize = bufsize;
|
||||
spin_lock_init(&client->buffer_lock);
|
||||
snprintf(client->name, sizeof(client->name), "%s-%d",
|
||||
dev_name(&evdev->dev), task_tgid_vnr(current));
|
||||
client->evdev = evdev;
|
||||
evdev_attach_client(evdev, client);
|
||||
|
||||
|
|
@ -386,6 +398,9 @@ static int evdev_fetch_next_event(struct evdev_client *client,
|
|||
if (have_event) {
|
||||
*event = client->buffer[client->tail++];
|
||||
client->tail &= client->bufsize - 1;
|
||||
if (client->use_wake_lock &&
|
||||
client->packet_head == client->tail)
|
||||
wake_unlock(&client->wake_lock);
|
||||
}
|
||||
|
||||
spin_unlock_irq(&client->buffer_lock);
|
||||
|
|
@ -674,6 +689,35 @@ static int evdev_handle_mt_request(struct input_dev *dev,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int evdev_enable_suspend_block(struct evdev *evdev,
|
||||
struct evdev_client *client)
|
||||
{
|
||||
if (client->use_wake_lock)
|
||||
return 0;
|
||||
|
||||
spin_lock_irq(&client->buffer_lock);
|
||||
wake_lock_init(&client->wake_lock, WAKE_LOCK_SUSPEND, client->name);
|
||||
client->use_wake_lock = true;
|
||||
if (client->packet_head != client->tail)
|
||||
wake_lock(&client->wake_lock);
|
||||
spin_unlock_irq(&client->buffer_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int evdev_disable_suspend_block(struct evdev *evdev,
|
||||
struct evdev_client *client)
|
||||
{
|
||||
if (!client->use_wake_lock)
|
||||
return 0;
|
||||
|
||||
spin_lock_irq(&client->buffer_lock);
|
||||
client->use_wake_lock = false;
|
||||
wake_lock_destroy(&client->wake_lock);
|
||||
spin_unlock_irq(&client->buffer_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long evdev_do_ioctl(struct file *file, unsigned int cmd,
|
||||
void __user *p, int compat_mode)
|
||||
{
|
||||
|
|
@ -755,6 +799,15 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd,
|
|||
|
||||
case EVIOCSKEYCODE_V2:
|
||||
return evdev_handle_set_keycode_v2(dev, p);
|
||||
|
||||
case EVIOCGSUSPENDBLOCK:
|
||||
return put_user(client->use_wake_lock, ip);
|
||||
|
||||
case EVIOCSSUSPENDBLOCK:
|
||||
if (p)
|
||||
return evdev_enable_suspend_block(evdev, client);
|
||||
else
|
||||
return evdev_disable_suspend_block(evdev, client);
|
||||
}
|
||||
|
||||
size = _IOC_SIZE(cmd);
|
||||
|
|
|
|||
239
drivers/input/keyreset.c
Normal file
239
drivers/input/keyreset.c
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
/* drivers/input/keyreset.c
|
||||
*
|
||||
* 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/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>
|
||||
|
||||
|
||||
struct keyreset_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;
|
||||
int key_down_target;
|
||||
int key_down;
|
||||
int key_up;
|
||||
int restart_disabled;
|
||||
int (*reset_fn)(void);
|
||||
};
|
||||
|
||||
int restart_requested;
|
||||
static void deferred_restart(struct work_struct *dummy)
|
||||
{
|
||||
restart_requested = 2;
|
||||
sys_sync();
|
||||
restart_requested = 3;
|
||||
kernel_restart(NULL);
|
||||
}
|
||||
static DECLARE_WORK(restart_work, deferred_restart);
|
||||
|
||||
static void keyreset_event(struct input_handle *handle, unsigned int type,
|
||||
unsigned int code, int value)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct keyreset_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->restart_disabled = 1;
|
||||
state->key_up++;
|
||||
} else
|
||||
state->key_up--;
|
||||
} else {
|
||||
if (value)
|
||||
state->key_down++;
|
||||
else
|
||||
state->key_down--;
|
||||
}
|
||||
if (state->key_down == 0 && state->key_up == 0)
|
||||
state->restart_disabled = 0;
|
||||
|
||||
pr_debug("reset key changed %d %d new state %d-%d-%d\n", code, value,
|
||||
state->key_down, state->key_up, state->restart_disabled);
|
||||
|
||||
if (value && !state->restart_disabled &&
|
||||
state->key_down == state->key_down_target) {
|
||||
state->restart_disabled = 1;
|
||||
if (restart_requested)
|
||||
panic("keyboard reset failed, %d", restart_requested);
|
||||
if (state->reset_fn) {
|
||||
restart_requested = state->reset_fn();
|
||||
} else {
|
||||
pr_info("keyboard reset\n");
|
||||
schedule_work(&restart_work);
|
||||
restart_requested = 1;
|
||||
}
|
||||
}
|
||||
done:
|
||||
spin_unlock_irqrestore(&state->lock, flags);
|
||||
}
|
||||
|
||||
static int keyreset_connect(struct input_handler *handler,
|
||||
struct input_dev *dev,
|
||||
const struct input_device_id *id)
|
||||
{
|
||||
int i;
|
||||
int ret;
|
||||
struct input_handle *handle;
|
||||
struct keyreset_state *state =
|
||||
container_of(handler, struct keyreset_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 = "keyreset";
|
||||
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;
|
||||
|
||||
pr_info("using input dev %s for key reset\n", dev->name);
|
||||
|
||||
return 0;
|
||||
|
||||
err_input_open_device:
|
||||
input_unregister_handle(handle);
|
||||
err_input_register_handle:
|
||||
kfree(handle);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void keyreset_disconnect(struct input_handle *handle)
|
||||
{
|
||||
input_close_device(handle);
|
||||
input_unregister_handle(handle);
|
||||
kfree(handle);
|
||||
}
|
||||
|
||||
static const struct input_device_id keyreset_ids[] = {
|
||||
{
|
||||
.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
|
||||
.evbit = { BIT_MASK(EV_KEY) },
|
||||
},
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(input, keyreset_ids);
|
||||
|
||||
static int keyreset_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
int key, *keyp;
|
||||
struct keyreset_state *state;
|
||||
struct keyreset_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);
|
||||
}
|
||||
}
|
||||
|
||||
if (pdata->reset_fn)
|
||||
state->reset_fn = pdata->reset_fn;
|
||||
|
||||
state->input_handler.event = keyreset_event;
|
||||
state->input_handler.connect = keyreset_connect;
|
||||
state->input_handler.disconnect = keyreset_disconnect;
|
||||
state->input_handler.name = KEYRESET_NAME;
|
||||
state->input_handler.id_table = keyreset_ids;
|
||||
ret = input_register_handler(&state->input_handler);
|
||||
if (ret) {
|
||||
kfree(state);
|
||||
return ret;
|
||||
}
|
||||
platform_set_drvdata(pdev, state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int keyreset_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct keyreset_state *state = platform_get_drvdata(pdev);
|
||||
input_unregister_handler(&state->input_handler);
|
||||
kfree(state);
|
||||
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);
|
||||
|
|
@ -299,6 +299,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
|
||||
|
|
@ -434,6 +445,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
|
||||
|
|
|
|||
|
|
@ -28,9 +28,11 @@ obj-$(CONFIG_INPUT_DA9055_ONKEY) += da9055_onkey.o
|
|||
obj-$(CONFIG_INPUT_DM355EVM) += dm355evm_keys.o
|
||||
obj-$(CONFIG_INPUT_GP2A) += gp2ap002a00f.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);
|
||||
|
|
@ -424,6 +424,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 X86 && HYPERVISOR_GUEST
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ obj-$(CONFIG_SENSORS_TSL2550) += tsl2550.o
|
|||
obj-$(CONFIG_EP93XX_PWM) += ep93xx_pwm.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/
|
||||
|
|
|
|||
152
drivers/misc/uid_stat.c
Normal file
152
drivers/misc/uid_stat.c
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
/* 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);
|
||||
return seq_printf(m, "%u\n", bytes);
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
@ -50,6 +50,15 @@ config MMC_BLOCK_BOUNCE
|
|||
|
||||
If unsure, say Y here.
|
||||
|
||||
config MMC_BLOCK_DEFERRED_RESUME
|
||||
bool "Deferr MMC layer resume until I/O is requested"
|
||||
depends on MMC_BLOCK
|
||||
default n
|
||||
help
|
||||
Say Y here to enable deferred MMC resume until I/O
|
||||
is requested. This will reduce overall resume latency and
|
||||
save power when theres an SD card inserted but not being used.
|
||||
|
||||
config SDIO_UART
|
||||
tristate "SDIO UART/GPS class support"
|
||||
depends on TTY
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@
|
|||
#include <linux/capability.h>
|
||||
#include <linux/compat.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>
|
||||
|
|
@ -163,11 +166,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;
|
||||
}
|
||||
|
||||
|
|
@ -728,18 +727,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:
|
||||
|
|
@ -1002,9 +1005,12 @@ static int mmc_blk_issue_secdiscard_rq(struct mmc_queue *mq,
|
|||
goto out;
|
||||
}
|
||||
|
||||
if (mmc_can_sanitize(card))
|
||||
if (mmc_can_sanitize(card)) {
|
||||
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, 0);
|
||||
trace_mmc_blk_erase_end(EXT_CSD_SANITIZE_START, 0, 0);
|
||||
}
|
||||
out_retry:
|
||||
if (err && !mmc_blk_reset(md, card->host, type))
|
||||
goto retry;
|
||||
|
|
@ -1893,6 +1899,11 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req)
|
|||
struct mmc_host *host = card->host;
|
||||
unsigned long flags;
|
||||
|
||||
#ifdef CONFIG_MMC_BLOCK_DEFERRED_RESUME
|
||||
if (mmc_bus_needs_resume(card->host))
|
||||
mmc_resume_bus(card->host);
|
||||
#endif
|
||||
|
||||
if (req && !mq->mqrq_prev->req)
|
||||
/* claim host only for the first request */
|
||||
mmc_claim_host(card->host);
|
||||
|
|
@ -2015,6 +2026,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)
|
||||
md->disk->flags |= GENHD_FL_NO_PART_SCAN;
|
||||
|
||||
|
|
@ -2329,6 +2341,9 @@ static int mmc_blk_probe(struct mmc_card *card)
|
|||
mmc_set_drvdata(card, md);
|
||||
mmc_fixup_device(card, blk_fixups);
|
||||
|
||||
#ifdef CONFIG_MMC_BLOCK_DEFERRED_RESUME
|
||||
mmc_set_bus_resume_policy(card->host, 1);
|
||||
#endif
|
||||
if (mmc_add_disk(md))
|
||||
goto out;
|
||||
|
||||
|
|
@ -2354,6 +2369,9 @@ static void mmc_blk_remove(struct mmc_card *card)
|
|||
mmc_release_host(card->host);
|
||||
mmc_blk_remove_req(md);
|
||||
mmc_set_drvdata(card, NULL);
|
||||
#ifdef CONFIG_MMC_BLOCK_DEFERRED_RESUME
|
||||
mmc_set_bus_resume_policy(card->host, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
|
|
|||
|
|
@ -26,3 +26,18 @@ config MMC_CLKGATE
|
|||
support handling this in order for it to be of any use.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@
|
|||
#include <linux/fault-inject.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/wakelock.h>
|
||||
|
||||
#include <trace/events/mmc.h>
|
||||
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/host.h>
|
||||
|
|
@ -172,6 +175,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) {
|
||||
|
|
@ -536,8 +540,12 @@ struct mmc_async_req *mmc_start_req(struct mmc_host *host,
|
|||
mmc_start_bkops(host->card, true);
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
@ -1591,6 +1599,36 @@ static inline void mmc_bus_put(struct mmc_host *host)
|
|||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
}
|
||||
|
||||
int mmc_resume_bus(struct mmc_host *host)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (!mmc_bus_needs_resume(host))
|
||||
return -EINVAL;
|
||||
|
||||
printk("%s: Starting deferred resume\n", mmc_hostname(host));
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
host->bus_resume_flags &= ~MMC_BUSRESUME_NEEDS_RESUME;
|
||||
host->rescan_disable = 0;
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
|
||||
mmc_bus_get(host);
|
||||
if (host->bus_ops && !host->bus_dead) {
|
||||
mmc_power_up(host);
|
||||
BUG_ON(!host->bus_ops->resume);
|
||||
host->bus_ops->resume(host);
|
||||
}
|
||||
|
||||
if (host->bus_ops->detect && !host->bus_dead)
|
||||
host->bus_ops->detect(host);
|
||||
|
||||
mmc_bus_put(host);
|
||||
printk("%s: Deferred resume completed\n", mmc_hostname(host));
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mmc_resume_bus);
|
||||
|
||||
/*
|
||||
* Assign a mmc bus handler to a host. Only one bus handler may control a
|
||||
* host at any given time.
|
||||
|
|
@ -1656,6 +1694,8 @@ void mmc_detect_change(struct mmc_host *host, unsigned long delay)
|
|||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
#endif
|
||||
host->detect_change = 1;
|
||||
|
||||
wake_lock(&host->detect_wake_lock);
|
||||
mmc_schedule_delayed_work(&host->detect, delay);
|
||||
}
|
||||
|
||||
|
|
@ -1815,8 +1855,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);
|
||||
|
||||
/*
|
||||
* qty is used to calculate the erase timeout which depends on how many
|
||||
* erase groups (or allocation units in SD terminology) are affected.
|
||||
|
|
@ -1920,6 +1965,8 @@ static int mmc_do_erase(struct mmc_card *card, unsigned int from,
|
|||
} while (!(cmd.resp[0] & R1_READY_FOR_DATA) ||
|
||||
(R1_CURRENT_STATE(cmd.resp[0]) == R1_STATE_PRG));
|
||||
out:
|
||||
|
||||
trace_mmc_blk_erase_end(arg, fr, nr);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
@ -2351,6 +2398,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->rescan_disable)
|
||||
return;
|
||||
|
|
@ -2372,6 +2420,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.
|
||||
|
|
@ -2400,16 +2454,24 @@ 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 (host->caps & MMC_CAP_NEEDS_POLL)
|
||||
if (extend_wakelock)
|
||||
wake_lock_timeout(&host->detect_wake_lock, HZ / 2);
|
||||
else
|
||||
wake_unlock(&host->detect_wake_lock);
|
||||
if (host->caps & MMC_CAP_NEEDS_POLL) {
|
||||
wake_lock(&host->detect_wake_lock);
|
||||
mmc_schedule_delayed_work(&host->detect, HZ);
|
||||
}
|
||||
}
|
||||
|
||||
void mmc_start_host(struct mmc_host *host)
|
||||
|
|
@ -2433,7 +2495,8 @@ void mmc_stop_host(struct mmc_host *host)
|
|||
#endif
|
||||
|
||||
host->rescan_disable = 1;
|
||||
cancel_delayed_work_sync(&host->detect);
|
||||
if (cancel_delayed_work_sync(&host->detect))
|
||||
wake_unlock(&host->detect_wake_lock);
|
||||
mmc_flush_scheduled_work();
|
||||
|
||||
/* clear pm flags now and let card drivers set them as needed */
|
||||
|
|
@ -2628,7 +2691,11 @@ int mmc_suspend_host(struct mmc_host *host)
|
|||
{
|
||||
int err = 0;
|
||||
|
||||
cancel_delayed_work(&host->detect);
|
||||
if (mmc_bus_needs_resume(host))
|
||||
return 0;
|
||||
|
||||
if (cancel_delayed_work(&host->detect))
|
||||
wake_unlock(&host->detect_wake_lock);
|
||||
mmc_flush_scheduled_work();
|
||||
|
||||
mmc_bus_get(host);
|
||||
|
|
@ -2679,6 +2746,12 @@ int mmc_resume_host(struct mmc_host *host)
|
|||
int err = 0;
|
||||
|
||||
mmc_bus_get(host);
|
||||
if (mmc_bus_manual_resume(host)) {
|
||||
host->bus_resume_flags |= MMC_BUSRESUME_NEEDS_RESUME;
|
||||
mmc_bus_put(host);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (host->bus_ops && !host->bus_dead) {
|
||||
if (!mmc_card_keep_power(host)) {
|
||||
mmc_power_up(host);
|
||||
|
|
@ -2739,9 +2812,14 @@ int mmc_pm_notify(struct notifier_block *notify_block,
|
|||
}
|
||||
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
if (mmc_bus_needs_resume(host)) {
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
break;
|
||||
}
|
||||
host->rescan_disable = 1;
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
cancel_delayed_work_sync(&host->detect);
|
||||
if (cancel_delayed_work_sync(&host->detect))
|
||||
wake_unlock(&host->detect_wake_lock);
|
||||
|
||||
if (!host->bus_ops || host->bus_ops->suspend)
|
||||
break;
|
||||
|
|
@ -2762,6 +2840,10 @@ int mmc_pm_notify(struct notifier_block *notify_block,
|
|||
case PM_POST_RESTORE:
|
||||
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
if (mmc_bus_manual_resume(host)) {
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
break;
|
||||
}
|
||||
host->rescan_disable = 0;
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
mmc_detect_change(host, 0);
|
||||
|
|
@ -2789,6 +2871,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;
|
||||
|
|
|
|||
|
|
@ -459,6 +459,8 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
|
|||
|
||||
spin_lock_init(&host->lock);
|
||||
init_waitqueue_head(&host->wq);
|
||||
wake_lock_init(&host->detect_wake_lock, WAKE_LOCK_SUSPEND,
|
||||
kasprintf(GFP_KERNEL, "%s_detect", mmc_hostname(host)));
|
||||
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
|
||||
#ifdef CONFIG_PM
|
||||
host->pm_notify.notifier_call = mmc_pm_notify;
|
||||
|
|
@ -511,7 +513,8 @@ int mmc_add_host(struct mmc_host *host)
|
|||
mmc_host_clk_sysfs_init(host);
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -528,7 +531,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
|
||||
|
|
@ -555,6 +560,7 @@ void mmc_free_host(struct mmc_host *host)
|
|||
spin_lock(&mmc_host_lock);
|
||||
idr_remove(&mmc_host_idr, host->index);
|
||||
spin_unlock(&mmc_host_lock);
|
||||
wake_lock_destroy(&host->detect_wake_lock);
|
||||
|
||||
put_device(&host->class_dev);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -805,6 +805,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) {
|
||||
/*
|
||||
|
|
@ -831,7 +834,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;
|
||||
}
|
||||
|
|
@ -1032,7 +1054,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);
|
||||
|
|
@ -1042,7 +1067,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_release_host(host);
|
||||
|
||||
|
|
@ -1084,12 +1125,31 @@ static int mmc_sd_suspend(struct mmc_host *host)
|
|||
static int mmc_sd_resume(struct mmc_host *host)
|
||||
{
|
||||
int err;
|
||||
#ifdef CONFIG_MMC_PARANOID_SD_INIT
|
||||
int retries;
|
||||
#endif
|
||||
|
||||
BUG_ON(!host);
|
||||
BUG_ON(!host->card);
|
||||
|
||||
mmc_claim_host(host);
|
||||
#ifdef CONFIG_MMC_PARANOID_SD_INIT
|
||||
retries = 5;
|
||||
while (retries) {
|
||||
err = mmc_sd_init_card(host, host->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->ocr, host->card);
|
||||
#endif
|
||||
mmc_release_host(host);
|
||||
|
||||
return err;
|
||||
|
|
@ -1143,6 +1203,9 @@ int mmc_attach_sd(struct mmc_host *host)
|
|||
{
|
||||
int err;
|
||||
u32 ocr;
|
||||
#ifdef CONFIG_MMC_PARANOID_SD_INIT
|
||||
int retries;
|
||||
#endif
|
||||
|
||||
BUG_ON(!host);
|
||||
WARN_ON(!host->claimed);
|
||||
|
|
@ -1198,9 +1261,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, host->ocr, 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, host->ocr, 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;
|
||||
|
|
@ -728,19 +733,35 @@ static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr,
|
|||
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 &&
|
||||
|
|
@ -1147,14 +1168,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)
|
||||
*/
|
||||
|
|
@ -1202,3 +1245,39 @@ int mmc_attach_sdio(struct mmc_host *host)
|
|||
return err;
|
||||
}
|
||||
|
||||
int sdio_reset_comm(struct mmc_card *card)
|
||||
{
|
||||
struct mmc_host *host = card->host;
|
||||
u32 ocr;
|
||||
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;
|
||||
|
||||
host->ocr = mmc_select_voltage(host, ocr);
|
||||
if (!host->ocr) {
|
||||
err = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
err = mmc_sdio_init_card(host, host->ocr, 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);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@
|
|||
#include "sdio_cis.h"
|
||||
#include "sdio_bus.h"
|
||||
|
||||
#ifdef CONFIG_MMC_EMBEDDED_SDIO
|
||||
#include <linux/mmc/host.h>
|
||||
#endif
|
||||
|
||||
/* show configuration fields */
|
||||
#define sdio_config_attr(field, format_string) \
|
||||
static ssize_t \
|
||||
|
|
@ -270,7 +274,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);
|
||||
|
||||
|
|
|
|||
33
drivers/mmc/core/sdio_io.c
Normal file → Executable file
33
drivers/mmc/core/sdio_io.c
Normal file → Executable file
|
|
@ -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
|
||||
|
||||
|
|
@ -133,9 +140,6 @@ config BCH_CONST_T
|
|||
default 8 if MTD_NAND_OMAP_BCH8
|
||||
endif
|
||||
|
||||
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
|
||||
tristate "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
|
||||
tristate "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
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user