diff options
author | John Stultz <john.stultz@linaro.org> | 2013-11-04 14:48:40 -0800 |
---|---|---|
committer | John Stultz <john.stultz@linaro.org> | 2013-11-04 14:48:40 -0800 |
commit | 354d86f4daecdc3553830e553f6fc17add736234 (patch) | |
tree | f486e22499fc5ce7548b76f299ecb91f52baf8ed | |
parent | a4b2b620070eb156f75c7d4b276e1acd8b7b3040 (diff) | |
parent | b38c6f06915c69424abb85fbd5c16e418865ed9b (diff) |
Merge branch 'linaro-fixes/android-3.10' into linaro-android.3.10-lsk
Merge "official" android-3.10 branch in w/ older experimental/android-3.10
to update lsk targetted branch to the official branch w/o any rebase trouble.
Signed-off-by: John Stultz <john.stultz@linaro.org>
34 files changed, 6524 insertions, 281 deletions
diff --git a/drivers/cpufreq/cpufreq_interactive.c b/drivers/cpufreq/cpufreq_interactive.c index a66748e13b3..d72e8c458f6 100644 --- a/drivers/cpufreq/cpufreq_interactive.c +++ b/drivers/cpufreq/cpufreq_interactive.c @@ -36,8 +36,6 @@ #define CREATE_TRACE_POINTS #include <trace/events/cpufreq_interactive.h> -static int active_count; - struct cpufreq_interactive_cpuinfo { struct timer_list cpu_timer; struct timer_list cpu_slack_timer; @@ -64,71 +62,62 @@ static cpumask_t speedchange_cpumask; static spinlock_t speedchange_cpumask_lock; static struct mutex gov_lock; -/* Hi speed to bump to from lo speed when load burst (default max) */ -static unsigned int hispeed_freq; - -/* Go to hi speed when CPU load at or above this value. */ -#define DEFAULT_GO_HISPEED_LOAD 99 -static unsigned long go_hispeed_load = DEFAULT_GO_HISPEED_LOAD; - /* Target load. Lower values result in higher CPU speeds. */ #define DEFAULT_TARGET_LOAD 90 static unsigned int default_target_loads[] = {DEFAULT_TARGET_LOAD}; -static spinlock_t target_loads_lock; -static unsigned int *target_loads = default_target_loads; -static int ntarget_loads = ARRAY_SIZE(default_target_loads); - -/* - * The minimum amount of time to spend at a frequency before we can ramp down. - */ -#define DEFAULT_MIN_SAMPLE_TIME (80 * USEC_PER_MSEC) -static unsigned long min_sample_time = DEFAULT_MIN_SAMPLE_TIME; -/* - * The sample rate of the timer used to increase frequency - */ #define DEFAULT_TIMER_RATE (20 * USEC_PER_MSEC) -static unsigned long timer_rate = DEFAULT_TIMER_RATE; - -/* - * Wait this long before raising speed above hispeed, by default a single - * timer interval. - */ #define DEFAULT_ABOVE_HISPEED_DELAY DEFAULT_TIMER_RATE static unsigned int default_above_hispeed_delay[] = { DEFAULT_ABOVE_HISPEED_DELAY }; -static spinlock_t above_hispeed_delay_lock; -static unsigned int *above_hispeed_delay = default_above_hispeed_delay; -static int nabove_hispeed_delay = ARRAY_SIZE(default_above_hispeed_delay); - -/* Non-zero means indefinite speed boost active */ -static int boost_val; -/* Duration of a boot pulse in usecs */ -static int boostpulse_duration_val = DEFAULT_MIN_SAMPLE_TIME; -/* End time of boost pulse in ktime converted to usecs */ -static u64 boostpulse_endtime; -/* - * Max additional time to wait in idle, beyond timer_rate, at speeds above - * minimum before wakeup to reduce speed, or -1 if unnecessary. - */ +struct cpufreq_interactive_tunables { + int usage_count; + /* Hi speed to bump to from lo speed when load burst (default max) */ + unsigned int hispeed_freq; + /* Go to hi speed when CPU load at or above this value. */ +#define DEFAULT_GO_HISPEED_LOAD 99 + unsigned long go_hispeed_load; + /* Target load. Lower values result in higher CPU speeds. */ + spinlock_t target_loads_lock; + unsigned int *target_loads; + int ntarget_loads; + /* + * The minimum amount of time to spend at a frequency before we can ramp + * down. + */ +#define DEFAULT_MIN_SAMPLE_TIME (80 * USEC_PER_MSEC) + unsigned long min_sample_time; + /* + * The sample rate of the timer used to increase frequency + */ + unsigned long timer_rate; + /* + * Wait this long before raising speed above hispeed, by default a + * single timer interval. + */ + spinlock_t above_hispeed_delay_lock; + unsigned int *above_hispeed_delay; + int nabove_hispeed_delay; + /* Non-zero means indefinite speed boost active */ + int boost_val; + /* Duration of a boot pulse in usecs */ + int boostpulse_duration_val; + /* End time of boost pulse in ktime converted to usecs */ + u64 boostpulse_endtime; + /* + * Max additional time to wait in idle, beyond timer_rate, at speeds + * above minimum before wakeup to reduce speed, or -1 if unnecessary. + */ #define DEFAULT_TIMER_SLACK (4 * DEFAULT_TIMER_RATE) -static int timer_slack_val = DEFAULT_TIMER_SLACK; - -static bool io_is_busy; + int timer_slack_val; + bool io_is_busy; +}; -static int cpufreq_governor_interactive(struct cpufreq_policy *policy, - unsigned int event); +/* For cases where we have single governor instance for system */ +struct cpufreq_interactive_tunables *common_tunables; -#ifndef CONFIG_CPU_FREQ_DEFAULT_GOV_INTERACTIVE -static -#endif -struct cpufreq_governor cpufreq_gov_interactive = { - .name = "interactive", - .governor = cpufreq_governor_interactive, - .max_transition_latency = 10000000, - .owner = THIS_MODULE, -}; +static struct attribute_group *get_sysfs_attr(void); static inline cputime64_t get_cpu_idle_time_jiffy(unsigned int cpu, cputime64_t *wall) @@ -153,8 +142,10 @@ static inline cputime64_t get_cpu_idle_time_jiffy(unsigned int cpu, return jiffies_to_usecs(idle_time); } -static inline cputime64_t get_cpu_idle_time(unsigned int cpu, - cputime64_t *wall) +static inline cputime64_t get_cpu_idle_time( + unsigned int cpu, + cputime64_t *wall, + bool io_is_busy) { u64 idle_time = get_cpu_idle_time_us(cpu, wall); @@ -169,20 +160,24 @@ static inline cputime64_t get_cpu_idle_time(unsigned int cpu, static void cpufreq_interactive_timer_resched( struct cpufreq_interactive_cpuinfo *pcpu) { + struct cpufreq_interactive_tunables *tunables = + pcpu->policy->governor_data; unsigned long expires; unsigned long flags; spin_lock_irqsave(&pcpu->load_lock, flags); pcpu->time_in_idle = get_cpu_idle_time(smp_processor_id(), - &pcpu->time_in_idle_timestamp); + &pcpu->time_in_idle_timestamp, + tunables->io_is_busy); pcpu->cputime_speedadj = 0; pcpu->cputime_speedadj_timestamp = pcpu->time_in_idle_timestamp; - expires = jiffies + usecs_to_jiffies(timer_rate); + expires = jiffies + usecs_to_jiffies(tunables->timer_rate); mod_timer_pinned(&pcpu->cpu_timer, expires); - if (timer_slack_val >= 0 && pcpu->target_freq > pcpu->policy->min) { - expires += usecs_to_jiffies(timer_slack_val); + if (tunables->timer_slack_val >= 0 && + pcpu->target_freq > pcpu->policy->min) { + expires += usecs_to_jiffies(tunables->timer_slack_val); mod_timer_pinned(&pcpu->cpu_slack_timer, expires); } @@ -193,58 +188,66 @@ static void cpufreq_interactive_timer_resched( * The cpu_timer and cpu_slack_timer must be deactivated when calling this * function. */ -static void cpufreq_interactive_timer_start(int cpu) +static void cpufreq_interactive_timer_start( + struct cpufreq_interactive_tunables *tunables, int cpu) { struct cpufreq_interactive_cpuinfo *pcpu = &per_cpu(cpuinfo, cpu); - unsigned long expires = jiffies + usecs_to_jiffies(timer_rate); + unsigned long expires = jiffies + + usecs_to_jiffies(tunables->timer_rate); unsigned long flags; pcpu->cpu_timer.expires = expires; add_timer_on(&pcpu->cpu_timer, cpu); - if (timer_slack_val >= 0 && pcpu->target_freq > pcpu->policy->min) { - expires += usecs_to_jiffies(timer_slack_val); + if (tunables->timer_slack_val >= 0 && + pcpu->target_freq > pcpu->policy->min) { + expires += usecs_to_jiffies(tunables->timer_slack_val); pcpu->cpu_slack_timer.expires = expires; add_timer_on(&pcpu->cpu_slack_timer, cpu); } spin_lock_irqsave(&pcpu->load_lock, flags); pcpu->time_in_idle = - get_cpu_idle_time(cpu, &pcpu->time_in_idle_timestamp); + get_cpu_idle_time(cpu, &pcpu->time_in_idle_timestamp, + tunables->io_is_busy); pcpu->cputime_speedadj = 0; pcpu->cputime_speedadj_timestamp = pcpu->time_in_idle_timestamp; spin_unlock_irqrestore(&pcpu->load_lock, flags); } -static unsigned int freq_to_above_hispeed_delay(unsigned int freq) +static unsigned int freq_to_above_hispeed_delay( + struct cpufreq_interactive_tunables *tunables, + unsigned int freq) { int i; unsigned int ret; unsigned long flags; - spin_lock_irqsave(&above_hispeed_delay_lock, flags); + spin_lock_irqsave(&tunables->above_hispeed_delay_lock, flags); - for (i = 0; i < nabove_hispeed_delay - 1 && - freq >= above_hispeed_delay[i+1]; i += 2) + for (i = 0; i < tunables->nabove_hispeed_delay - 1 && + freq >= tunables->above_hispeed_delay[i+1]; i += 2) ; - ret = above_hispeed_delay[i]; - spin_unlock_irqrestore(&above_hispeed_delay_lock, flags); + ret = tunables->above_hispeed_delay[i]; + spin_unlock_irqrestore(&tunables->above_hispeed_delay_lock, flags); return ret; } -static unsigned int freq_to_targetload(unsigned int freq) +static unsigned int freq_to_targetload( + struct cpufreq_interactive_tunables *tunables, unsigned int freq) { int i; unsigned int ret; unsigned long flags; - spin_lock_irqsave(&target_loads_lock, flags); + spin_lock_irqsave(&tunables->target_loads_lock, flags); - for (i = 0; i < ntarget_loads - 1 && freq >= target_loads[i+1]; i += 2) + for (i = 0; i < tunables->ntarget_loads - 1 && + freq >= tunables->target_loads[i+1]; i += 2) ; - ret = target_loads[i]; - spin_unlock_irqrestore(&target_loads_lock, flags); + ret = tunables->target_loads[i]; + spin_unlock_irqrestore(&tunables->target_loads_lock, flags); return ret; } @@ -253,9 +256,8 @@ static unsigned int freq_to_targetload(unsigned int freq) * choose_freq() will find the minimum frequency that does not exceed its * target load given the current load. */ - -static unsigned int choose_freq( - struct cpufreq_interactive_cpuinfo *pcpu, unsigned int loadadjfreq) +static unsigned int choose_freq(struct cpufreq_interactive_cpuinfo *pcpu, + unsigned int loadadjfreq) { unsigned int freq = pcpu->policy->cur; unsigned int prevfreq, freqmin, freqmax; @@ -267,7 +269,7 @@ static unsigned int choose_freq( do { prevfreq = freq; - tl = freq_to_targetload(freq); + tl = freq_to_targetload(pcpu->policy->governor_data, freq); /* * Find the lowest frequency where the computed load is less @@ -342,13 +344,15 @@ static unsigned int choose_freq( static u64 update_load(int cpu) { struct cpufreq_interactive_cpuinfo *pcpu = &per_cpu(cpuinfo, cpu); + struct cpufreq_interactive_tunables *tunables = + pcpu->policy->governor_data; u64 now; u64 now_idle; unsigned int delta_idle; unsigned int delta_time; u64 active_time; - now_idle = get_cpu_idle_time(cpu, &now); + now_idle = get_cpu_idle_time(cpu, &now, tunables->io_is_busy); delta_idle = (unsigned int)(now_idle - pcpu->time_in_idle); delta_time = (unsigned int)(now - pcpu->time_in_idle_timestamp); @@ -372,6 +376,8 @@ static void cpufreq_interactive_timer(unsigned long data) int cpu_load; struct cpufreq_interactive_cpuinfo *pcpu = &per_cpu(cpuinfo, data); + struct cpufreq_interactive_tunables *tunables = + pcpu->policy->governor_data; unsigned int new_freq; unsigned int loadadjfreq; unsigned int index; @@ -395,25 +401,25 @@ static void cpufreq_interactive_timer(unsigned long data) do_div(cputime_speedadj, delta_time); loadadjfreq = (unsigned int)cputime_speedadj * 100; cpu_load = loadadjfreq / pcpu->target_freq; - boosted = boost_val || now < boostpulse_endtime; + boosted = tunables->boost_val || now < tunables->boostpulse_endtime; - if (cpu_load >= go_hispeed_load || boosted) { - if (pcpu->target_freq < hispeed_freq) { - new_freq = hispeed_freq; + if (cpu_load >= tunables->go_hispeed_load || boosted) { + if (pcpu->target_freq < tunables->hispeed_freq) { + new_freq = tunables->hispeed_freq; } else { new_freq = choose_freq(pcpu, loadadjfreq); - if (new_freq < hispeed_freq) - new_freq = hispeed_freq; + if (new_freq < tunables->hispeed_freq) + new_freq = tunables->hispeed_freq; } } else { new_freq = choose_freq(pcpu, loadadjfreq); } - if (pcpu->target_freq >= hispeed_freq && + if (pcpu->target_freq >= tunables->hispeed_freq && new_freq > pcpu->target_freq && now - pcpu->hispeed_validate_time < - freq_to_above_hispeed_delay(pcpu->target_freq)) { + freq_to_above_hispeed_delay(tunables, pcpu->target_freq)) { trace_cpufreq_interactive_notyet( data, cpu_load, pcpu->target_freq, pcpu->policy->cur, new_freq); @@ -434,7 +440,8 @@ static void cpufreq_interactive_timer(unsigned long data) * floor frequency for the minimum sample time since last validated. */ if (new_freq < pcpu->floor_freq) { - if (now - pcpu->floor_validate_time < min_sample_time) { + if (now - pcpu->floor_validate_time < + tunables->min_sample_time) { trace_cpufreq_interactive_notyet( data, cpu_load, pcpu->target_freq, pcpu->policy->cur, new_freq); @@ -450,7 +457,7 @@ static void cpufreq_interactive_timer(unsigned long data) * (or the indefinite boost is turned off). */ - if (!boosted || new_freq > hispeed_freq) { + if (!boosted || new_freq > tunables->hispeed_freq) { pcpu->floor_freq = new_freq; pcpu->floor_validate_time = now; } @@ -611,14 +618,16 @@ static void cpufreq_interactive_boost(void) int anyboost = 0; unsigned long flags; struct cpufreq_interactive_cpuinfo *pcpu; + struct cpufreq_interactive_tunables *tunables; spin_lock_irqsave(&speedchange_cpumask_lock, flags); for_each_online_cpu(i) { pcpu = &per_cpu(cpuinfo, i); + tunables = pcpu->policy->governor_data; - if (pcpu->target_freq < hispeed_freq) { - pcpu->target_freq = hispeed_freq; + if (pcpu->target_freq < tunables->hispeed_freq) { + pcpu->target_freq = tunables->hispeed_freq; cpumask_set_cpu(i, &speedchange_cpumask); pcpu->hispeed_validate_time = ktime_to_us(ktime_get()); @@ -630,7 +639,7 @@ static void cpufreq_interactive_boost(void) * validated. */ - pcpu->floor_freq = hispeed_freq; + pcpu->floor_freq = tunables->hispeed_freq; pcpu->floor_validate_time = ktime_to_us(ktime_get()); } @@ -730,26 +739,27 @@ err: } static ssize_t show_target_loads( - struct kobject *kobj, struct attribute *attr, char *buf) + struct cpufreq_interactive_tunables *tunables, + char *buf) { int i; ssize_t ret = 0; unsigned long flags; - spin_lock_irqsave(&target_loads_lock, flags); + spin_lock_irqsave(&tunables->target_loads_lock, flags); - for (i = 0; i < ntarget_loads; i++) - ret += sprintf(buf + ret, "%u%s", target_loads[i], + for (i = 0; i < tunables->ntarget_loads; i++) + ret += sprintf(buf + ret, "%u%s", tunables->target_loads[i], i & 0x1 ? ":" : " "); ret += sprintf(buf + --ret, "\n"); - spin_unlock_irqrestore(&target_loads_lock, flags); + spin_unlock_irqrestore(&tunables->target_loads_lock, flags); return ret; } static ssize_t store_target_loads( - struct kobject *kobj, struct attribute *attr, const char *buf, - size_t count) + struct cpufreq_interactive_tunables *tunables, + const char *buf, size_t count) { int ntokens; unsigned int *new_target_loads = NULL; @@ -759,40 +769,37 @@ static ssize_t store_target_loads( if (IS_ERR(new_target_loads)) return PTR_RET(new_target_loads); - spin_lock_irqsave(&target_loads_lock, flags); - if (target_loads != default_target_loads) - kfree(target_loads); - target_loads = new_target_loads; - ntarget_loads = ntokens; - spin_unlock_irqrestore(&target_loads_lock, flags); + spin_lock_irqsave(&tunables->target_loads_lock, flags); + if (tunables->target_loads != default_target_loads) + kfree(tunables->target_loads); + tunables->target_loads = new_target_loads; + tunables->ntarget_loads = ntokens; + spin_unlock_irqrestore(&tunables->target_loads_lock, flags); return count; } -static struct global_attr target_loads_attr = - __ATTR(target_loads, S_IRUGO | S_IWUSR, - show_target_loads, store_target_loads); - static ssize_t show_above_hispeed_delay( - struct kobject *kobj, struct attribute *attr, char *buf) + struct cpufreq_interactive_tunables *tunables, char *buf) { int i; ssize_t ret = 0; unsigned long flags; - spin_lock_irqsave(&above_hispeed_delay_lock, flags); + spin_lock_irqsave(&tunables->above_hispeed_delay_lock, flags); - for (i = 0; i < nabove_hispeed_delay; i++) - ret += sprintf(buf + ret, "%u%s", above_hispeed_delay[i], + for (i = 0; i < tunables->nabove_hispeed_delay; i++) + ret += sprintf(buf + ret, "%u%s", + tunables->above_hispeed_delay[i], i & 0x1 ? ":" : " "); ret += sprintf(buf + --ret, "\n"); - spin_unlock_irqrestore(&above_hispeed_delay_lock, flags); + spin_unlock_irqrestore(&tunables->above_hispeed_delay_lock, flags); return ret; } static ssize_t store_above_hispeed_delay( - struct kobject *kobj, struct attribute *attr, const char *buf, - size_t count) + struct cpufreq_interactive_tunables *tunables, + const char *buf, size_t count) { int ntokens; unsigned int *new_above_hispeed_delay = NULL; @@ -802,29 +809,24 @@ static ssize_t store_above_hispeed_delay( if (IS_ERR(new_above_hispeed_delay)) return PTR_RET(new_above_hispeed_delay); - spin_lock_irqsave(&above_hispeed_delay_lock, flags); - if (above_hispeed_delay != default_above_hispeed_delay) - kfree(above_hispeed_delay); - above_hispeed_delay = new_above_hispeed_delay; - nabove_hispeed_delay = ntokens; - spin_unlock_irqrestore(&above_hispeed_delay_lock, flags); + spin_lock_irqsave(&tunables->above_hispeed_delay_lock, flags); + if (tunables->above_hispeed_delay != default_above_hispeed_delay) + kfree(tunables->above_hispeed_delay); + tunables->above_hispeed_delay = new_above_hispeed_delay; + tunables->nabove_hispeed_delay = ntokens; + spin_unlock_irqrestore(&tunables->above_hispeed_delay_lock, flags); return count; } -static struct global_attr above_hispeed_delay_attr = - __ATTR(above_hispeed_delay, S_IRUGO | S_IWUSR, - show_above_hispeed_delay, store_above_hispeed_delay); - -static ssize_t show_hispeed_freq(struct kobject *kobj, - struct attribute *attr, char *buf) +static ssize_t show_hispeed_freq(struct cpufreq_interactive_tunables *tunables, + char *buf) { - return sprintf(buf, "%u\n", hispeed_freq); + return sprintf(buf, "%u\n", tunables->hispeed_freq); } -static ssize_t store_hispeed_freq(struct kobject *kobj, - struct attribute *attr, const char *buf, - size_t count) +static ssize_t store_hispeed_freq(struct cpufreq_interactive_tunables *tunables, + const char *buf, size_t count) { int ret; long unsigned int val; @@ -832,22 +834,18 @@ static ssize_t store_hispeed_freq(struct kobject *kobj, ret = strict_strtoul(buf, 0, &val); if (ret < 0) return ret; - hispeed_freq = val; + tunables->hispeed_freq = val; return count; } -static struct global_attr hispeed_freq_attr = __ATTR(hispeed_freq, 0644, - show_hispeed_freq, store_hispeed_freq); - - -static ssize_t show_go_hispeed_load(struct kobject *kobj, - struct attribute *attr, char *buf) +static ssize_t show_go_hispeed_load(struct cpufreq_interactive_tunables + *tunables, char *buf) { - return sprintf(buf, "%lu\n", go_hispeed_load); + return sprintf(buf, "%lu\n", tunables->go_hispeed_load); } -static ssize_t store_go_hispeed_load(struct kobject *kobj, - struct attribute *attr, const char *buf, size_t count) +static ssize_t store_go_hispeed_load(struct cpufreq_interactive_tunables + *tunables, const char *buf, size_t count) { int ret; unsigned long val; @@ -855,21 +853,18 @@ static ssize_t store_go_hispeed_load(struct kobject *kobj, ret = strict_strtoul(buf, 0, &val); if (ret < 0) return ret; - go_hispeed_load = val; + tunables->go_hispeed_load = val; return count; } -static struct global_attr go_hispeed_load_attr = __ATTR(go_hispeed_load, 0644, - show_go_hispeed_load, store_go_hispeed_load); - -static ssize_t show_min_sample_time(struct kobject *kobj, - struct attribute *attr, char *buf) +static ssize_t show_min_sample_time(struct cpufreq_interactive_tunables + *tunables, char *buf) { - return sprintf(buf, "%lu\n", min_sample_time); + return sprintf(buf, "%lu\n", tunables->min_sample_time); } -static ssize_t store_min_sample_time(struct kobject *kobj, - struct attribute *attr, const char *buf, size_t count) +static ssize_t store_min_sample_time(struct cpufreq_interactive_tunables + *tunables, const char *buf, size_t count) { int ret; unsigned long val; @@ -877,21 +872,18 @@ static ssize_t store_min_sample_time(struct kobject *kobj, ret = strict_strtoul(buf, 0, &val); if (ret < 0) return ret; - min_sample_time = val; + tunables->min_sample_time = val; return count; } -static struct global_attr min_sample_time_attr = __ATTR(min_sample_time, 0644, - show_min_sample_time, store_min_sample_time); - -static ssize_t show_timer_rate(struct kobject *kobj, - struct attribute *attr, char *buf) +static ssize_t show_timer_rate(struct cpufreq_interactive_tunables *tunables, + char *buf) { - return sprintf(buf, "%lu\n", timer_rate); + return sprintf(buf, "%lu\n", tunables->timer_rate); } -static ssize_t store_timer_rate(struct kobject *kobj, - struct attribute *attr, const char *buf, size_t count) +static ssize_t store_timer_rate(struct cpufreq_interactive_tunables *tunables, + const char *buf, size_t count) { int ret; unsigned long val; @@ -899,22 +891,18 @@ static ssize_t store_timer_rate(struct kobject *kobj, ret = strict_strtoul(buf, 0, &val); if (ret < 0) return ret; - timer_rate = val; + tunables->timer_rate = val; return count; } -static struct global_attr timer_rate_attr = __ATTR(timer_rate, 0644, - show_timer_rate, store_timer_rate); - -static ssize_t show_timer_slack( - struct kobject *kobj, struct attribute *attr, char *buf) +static ssize_t show_timer_slack(struct cpufreq_interactive_tunables *tunables, + char *buf) { - return sprintf(buf, "%d\n", timer_slack_val); + return sprintf(buf, "%d\n", tunables->timer_slack_val); } -static ssize_t store_timer_slack( - struct kobject *kobj, struct attribute *attr, const char *buf, - size_t count) +static ssize_t store_timer_slack(struct cpufreq_interactive_tunables *tunables, + const char *buf, size_t count) { int ret; unsigned long val; @@ -923,19 +911,17 @@ static ssize_t store_timer_slack( if (ret < 0) return ret; - timer_slack_val = val; + tunables->timer_slack_val = val; return count; } -define_one_global_rw(timer_slack); - -static ssize_t show_boost(struct kobject *kobj, struct attribute *attr, +static ssize_t show_boost(struct cpufreq_interactive_tunables *tunables, char *buf) { - return sprintf(buf, "%d\n", boost_val); + return sprintf(buf, "%d\n", tunables->boost_val); } -static ssize_t store_boost(struct kobject *kobj, struct attribute *attr, +static ssize_t store_boost(struct cpufreq_interactive_tunables *tunables, const char *buf, size_t count) { int ret; @@ -945,9 +931,9 @@ static ssize_t store_boost(struct kobject *kobj, struct attribute *attr, if (ret < 0) return ret; - boost_val = val; + tunables->boost_val = val; - if (boost_val) { + if (tunables->boost_val) { trace_cpufreq_interactive_boost("on"); cpufreq_interactive_boost(); } else { @@ -957,9 +943,7 @@ static ssize_t store_boost(struct kobject *kobj, struct attribute *attr, return count; } -define_one_global_rw(boost); - -static ssize_t store_boostpulse(struct kobject *kobj, struct attribute *attr, +static ssize_t store_boostpulse(struct cpufreq_interactive_tunables *tunables, const char *buf, size_t count) { int ret; @@ -969,24 +953,21 @@ static ssize_t store_boostpulse(struct kobject *kobj, struct attribute *attr, if (ret < 0) return ret; - boostpulse_endtime = ktime_to_us(ktime_get()) + boostpulse_duration_val; + tunables->boostpulse_endtime = ktime_to_us(ktime_get()) + + tunables->boostpulse_duration_val; trace_cpufreq_interactive_boost("pulse"); cpufreq_interactive_boost(); return count; } -static struct global_attr boostpulse = - __ATTR(boostpulse, 0200, NULL, store_boostpulse); - -static ssize_t show_boostpulse_duration( - struct kobject *kobj, struct attribute *attr, char *buf) +static ssize_t show_boostpulse_duration(struct cpufreq_interactive_tunables + *tunables, char *buf) { - return sprintf(buf, "%d\n", boostpulse_duration_val); + return sprintf(buf, "%d\n", tunables->boostpulse_duration_val); } -static ssize_t store_boostpulse_duration( - struct kobject *kobj, struct attribute *attr, const char *buf, - size_t count) +static ssize_t store_boostpulse_duration(struct cpufreq_interactive_tunables + *tunables, const char *buf, size_t count) { int ret; unsigned long val; @@ -995,20 +976,18 @@ static ssize_t store_boostpulse_duration( if (ret < 0) return ret; - boostpulse_duration_val = val; + tunables->boostpulse_duration_val = val; return count; } -define_one_global_rw(boostpulse_duration); - -static ssize_t show_io_is_busy(struct kobject *kobj, - struct attribute *attr, char *buf) +static ssize_t show_io_is_busy(struct cpufreq_interactive_tunables *tunables, + char *buf) { - return sprintf(buf, "%u\n", io_is_busy); + return sprintf(buf, "%u\n", tunables->io_is_busy); } -static ssize_t store_io_is_busy(struct kobject *kobj, - struct attribute *attr, const char *buf, size_t count) +static ssize_t store_io_is_busy(struct cpufreq_interactive_tunables *tunables, + const char *buf, size_t count) { int ret; unsigned long val; @@ -1016,33 +995,137 @@ static ssize_t store_io_is_busy(struct kobject *kobj, ret = kstrtoul(buf, 0, &val); if (ret < 0) return ret; - io_is_busy = val; + tunables->io_is_busy = val; return count; } -static struct global_attr io_is_busy_attr = __ATTR(io_is_busy, 0644, - show_io_is_busy, store_io_is_busy); - -static struct attribute *interactive_attributes[] = { - &target_loads_attr.attr, - &above_hispeed_delay_attr.attr, - &hispeed_freq_attr.attr, - &go_hispeed_load_attr.attr, - &min_sample_time_attr.attr, - &timer_rate_attr.attr, - &timer_slack.attr, - &boost.attr, - &boostpulse.attr, - &boostpulse_duration.attr, - &io_is_busy_attr.attr, +/* + * Create show/store routines + * - sys: One governor instance for complete SYSTEM + * - pol: One governor instance per struct cpufreq_policy + */ +#define show_gov_pol_sys(file_name) \ +static ssize_t show_##file_name##_gov_sys \ +(struct kobject *kobj, struct attribute *attr, char *buf) \ +{ \ + return show_##file_name(common_tunables, buf); \ +} \ + \ +static ssize_t show_##file_name##_gov_pol \ +(struct cpufreq_policy *policy, char *buf) \ +{ \ + return show_##file_name(policy->governor_data, buf); \ +} + +#define store_gov_pol_sys(file_name) \ +static ssize_t store_##file_name##_gov_sys \ +(struct kobject *kobj, struct attribute *attr, const char *buf, \ + size_t count) \ +{ \ + return store_##file_name(common_tunables, buf, count); \ +} \ + \ +static ssize_t store_##file_name##_gov_pol \ +(struct cpufreq_policy *policy, const char *buf, size_t count) \ +{ \ + return store_##file_name(policy->governor_data, buf, count); \ +} + +#define show_store_gov_pol_sys(file_name) \ +show_gov_pol_sys(file_name); \ +store_gov_pol_sys(file_name) + +show_store_gov_pol_sys(target_loads); +show_store_gov_pol_sys(above_hispeed_delay); +show_store_gov_pol_sys(hispeed_freq); +show_store_gov_pol_sys(go_hispeed_load); +show_store_gov_pol_sys(min_sample_time); +show_store_gov_pol_sys(timer_rate); +show_store_gov_pol_sys(timer_slack); +show_store_gov_pol_sys(boost); +store_gov_pol_sys(boostpulse); +show_store_gov_pol_sys(boostpulse_duration); +show_store_gov_pol_sys(io_is_busy); + +#define gov_sys_attr_rw(_name) \ +static struct global_attr _name##_gov_sys = \ +__ATTR(_name, 0644, show_##_name##_gov_sys, store_##_name##_gov_sys) + +#define gov_pol_attr_rw(_name) \ +static struct freq_attr _name##_gov_pol = \ +__ATTR(_name, 0644, show_##_name##_gov_pol, store_##_name##_gov_pol) + +#define gov_sys_pol_attr_rw(_name) \ + gov_sys_attr_rw(_name); \ + gov_pol_attr_rw(_name) + +gov_sys_pol_attr_rw(target_loads); +gov_sys_pol_attr_rw(above_hispeed_delay); +gov_sys_pol_attr_rw(hispeed_freq); +gov_sys_pol_attr_rw(go_hispeed_load); +gov_sys_pol_attr_rw(min_sample_time); +gov_sys_pol_attr_rw(timer_rate); +gov_sys_pol_attr_rw(timer_slack); +gov_sys_pol_attr_rw(boost); +gov_sys_pol_attr_rw(boostpulse_duration); +gov_sys_pol_attr_rw(io_is_busy); + +static struct global_attr boostpulse_gov_sys = + __ATTR(boostpulse, 0200, NULL, store_boostpulse_gov_sys); + +static struct freq_attr boostpulse_gov_pol = + __ATTR(boostpulse, 0200, NULL, store_boostpulse_gov_pol); + +/* One Governor instance for entire system */ +static struct attribute *interactive_attributes_gov_sys[] = { + &target_loads_gov_sys.attr, + &above_hispeed_delay_gov_sys.attr, + &hispeed_freq_gov_sys.attr, + &go_hispeed_load_gov_sys.attr, + &min_sample_time_gov_sys.attr, + &timer_rate_gov_sys.attr, + &timer_slack_gov_sys.attr, + &boost_gov_sys.attr, + &boostpulse_gov_sys.attr, + &boostpulse_duration_gov_sys.attr, + &io_is_busy_gov_sys.attr, NULL, }; -static struct attribute_group interactive_attr_group = { - .attrs = interactive_attributes, +static struct attribute_group interactive_attr_group_gov_sys = { + .attrs = interactive_attributes_gov_sys, .name = "interactive", }; +/* Per policy governor instance */ +static struct attribute *interactive_attributes_gov_pol[] = { + &target_loads_gov_pol.attr, + &above_hispeed_delay_gov_pol.attr, + &hispeed_freq_gov_pol.attr, + &go_hispeed_load_gov_pol.attr, + &min_sample_time_gov_pol.attr, + &timer_rate_gov_pol.attr, + &timer_slack_gov_pol.attr, + &boost_gov_pol.attr, + &boostpulse_gov_pol.attr, + &boostpulse_duration_gov_pol.attr, + &io_is_busy_gov_pol.attr, + NULL, +}; + +static struct attribute_group interactive_attr_group_gov_pol = { + .attrs = interactive_attributes_gov_pol, + .name = "interactive", +}; + +static struct attribute_group *get_sysfs_attr(void) +{ + if (have_governor_per_policy()) + return &interactive_attr_group_gov_pol; + else + return &interactive_attr_group_gov_sys; +} + static int cpufreq_interactive_idle_notifier(struct notifier_block *nb, unsigned long val, void *data) @@ -1070,18 +1153,88 @@ static int cpufreq_governor_interactive(struct cpufreq_policy *policy, unsigned int j; struct cpufreq_interactive_cpuinfo *pcpu; struct cpufreq_frequency_table *freq_table; + struct cpufreq_interactive_tunables *tunables; + + if (have_governor_per_policy()) + tunables = policy->governor_data; + else + tunables = common_tunables; + + WARN_ON(!tunables && (event != CPUFREQ_GOV_POLICY_INIT)); switch (event) { - case CPUFREQ_GOV_START: - if (!cpu_online(policy->cpu)) - return -EINVAL; + case CPUFREQ_GOV_POLICY_INIT: + if (have_governor_per_policy()) { + WARN_ON(tunables); + } else if (tunables) { + tunables->usage_count++; + policy->governor_data = tunables; + return 0; + } + + tunables = kzalloc(sizeof(*tunables), GFP_KERNEL); + if (!tunables) { + pr_err("%s: POLICY_INIT: kzalloc failed\n", __func__); + return -ENOMEM; + } + + rc = sysfs_create_group(get_governor_parent_kobj(policy), + get_sysfs_attr()); + if (rc) { + kfree(tunables); + return rc; + } + + tunables->usage_count = 1; + tunables->above_hispeed_delay = default_above_hispeed_delay; + tunables->nabove_hispeed_delay = + ARRAY_SIZE(default_above_hispeed_delay); + tunables->go_hispeed_load = DEFAULT_GO_HISPEED_LOAD; + tunables->target_loads = default_target_loads; + tunables->ntarget_loads = ARRAY_SIZE(default_target_loads); + tunables->min_sample_time = DEFAULT_MIN_SAMPLE_TIME; + tunables->timer_rate = DEFAULT_TIMER_RATE; + tunables->boostpulse_duration_val = DEFAULT_MIN_SAMPLE_TIME; + tunables->timer_slack_val = DEFAULT_TIMER_SLACK; + + spin_lock_init(&tunables->target_loads_lock); + spin_lock_init(&tunables->above_hispeed_delay_lock); + + if (!policy->governor->initialized) { + idle_notifier_register(&cpufreq_interactive_idle_nb); + cpufreq_register_notifier(&cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + } + + policy->governor_data = tunables; + if (!have_governor_per_policy()) + common_tunables = tunables; + + break; + + case CPUFREQ_GOV_POLICY_EXIT: + if (!--tunables->usage_count) { + if (policy->governor->initialized == 1) { + cpufreq_unregister_notifier(&cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + idle_notifier_unregister(&cpufreq_interactive_idle_nb); + } + + sysfs_remove_group(get_governor_parent_kobj(policy), + get_sysfs_attr()); + kfree(tunables); + common_tunables = NULL; + } + policy->governor_data = NULL; + break; + + case CPUFREQ_GOV_START: mutex_lock(&gov_lock); - freq_table = - cpufreq_frequency_get_table(policy->cpu); - if (!hispeed_freq) - hispeed_freq = policy->max; + freq_table = cpufreq_frequency_get_table(policy->cpu); + if (!tunables->hispeed_freq) + tunables->hispeed_freq = policy->max; for_each_cpu(j, policy->cpus) { pcpu = &per_cpu(cpuinfo, j); @@ -1094,30 +1247,13 @@ static int cpufreq_governor_interactive(struct cpufreq_policy *policy, pcpu->hispeed_validate_time = pcpu->floor_validate_time; down_write(&pcpu->enable_sem); - cpufreq_interactive_timer_start(j); + del_timer_sync(&pcpu->cpu_timer); + del_timer_sync(&pcpu->cpu_slack_timer); + cpufreq_interactive_timer_start(tunables, j); pcpu->governor_enabled = 1; up_write(&pcpu->enable_sem); } - /* - * Do not register the idle hook and create sysfs - * entries if we have already done so. - */ - if (++active_count > 1) { - mutex_unlock(&gov_lock); - return 0; - } - - rc = sysfs_create_group(cpufreq_global_kobject, - &interactive_attr_group); - if (rc) { - mutex_unlock(&gov_lock); - return rc; - } - - idle_notifier_register(&cpufreq_interactive_idle_nb); - cpufreq_register_notifier( - &cpufreq_notifier_block, CPUFREQ_TRANSITION_NOTIFIER); mutex_unlock(&gov_lock); break; @@ -1132,18 +1268,7 @@ static int cpufreq_governor_interactive(struct cpufreq_policy *policy, up_write(&pcpu->enable_sem); } - if (--active_count > 0) { - mutex_unlock(&gov_lock); - return 0; - } - - cpufreq_unregister_notifier( - &cpufreq_notifier_block, CPUFREQ_TRANSITION_NOTIFIER); - idle_notifier_unregister(&cpufreq_interactive_idle_nb); - sysfs_remove_group(cpufreq_global_kobject, - &interactive_attr_group); mutex_unlock(&gov_lock); - break; case CPUFREQ_GOV_LIMITS: @@ -1177,7 +1302,7 @@ static int cpufreq_governor_interactive(struct cpufreq_policy *policy, */ del_timer_sync(&pcpu->cpu_timer); del_timer_sync(&pcpu->cpu_slack_timer); - cpufreq_interactive_timer_start(j); + cpufreq_interactive_timer_start(tunables, j); up_write(&pcpu->enable_sem); } break; @@ -1185,6 +1310,16 @@ static int cpufreq_governor_interactive(struct cpufreq_policy *policy, return 0; } +#ifndef CONFIG_CPU_FREQ_DEFAULT_GOV_INTERACTIVE +static +#endif +struct cpufreq_governor cpufreq_gov_interactive = { + .name = "interactive", + .governor = cpufreq_governor_interactive, + .max_transition_latency = 10000000, + .owner = THIS_MODULE, +}; + static void cpufreq_interactive_nop_timer(unsigned long data) { } @@ -1207,9 +1342,7 @@ static int __init cpufreq_interactive_init(void) init_rwsem(&pcpu->enable_sem); } - spin_lock_init(&target_loads_lock); spin_lock_init(&speedchange_cpumask_lock); - spin_lock_init(&above_hispeed_delay_lock); mutex_init(&gov_lock); speedchange_task = kthread_create(cpufreq_interactive_speedchange_task, NULL, diff --git a/drivers/gpu/ion/Makefile b/drivers/gpu/ion/Makefile index ce0f1ef1c23..9c956659124 100644 --- a/drivers/gpu/ion/Makefile +++ b/drivers/gpu/ion/Makefile @@ -1,3 +1,6 @@ obj-$(CONFIG_ION) += ion.o ion_heap.o ion_page_pool.o ion_system_heap.o \ ion_carveout_heap.o ion_chunk_heap.o ion_cma_heap.o +ifdef CONFIG_COMPAT +obj-$(CONFIG_ION) += compat_ion.o +endif obj-$(CONFIG_ION_TEGRA) += tegra/ diff --git a/drivers/gpu/ion/compat_ion.c b/drivers/gpu/ion/compat_ion.c new file mode 100644 index 00000000000..e0d2839952a --- /dev/null +++ b/drivers/gpu/ion/compat_ion.c @@ -0,0 +1,172 @@ +/* + * drivers/gpu/ion/compat_ion.c + * + * Copyright (C) 2013 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/ion.h> +#include <linux/compat.h> +#include <linux/fs.h> +#include <linux/uaccess.h> + +#include "compat_ion.h" + +/* See include/linux/ion.h for the definition of these structs */ +struct compat_ion_allocation_data { + compat_size_t len; + compat_size_t align; + compat_uint_t heap_id_mask; + compat_uint_t flags; + compat_int_t handle; +}; + +struct compat_ion_custom_data { + compat_uint_t cmd; + compat_ulong_t arg; +}; + +static int compat_get_ion_allocation_data( + struct compat_ion_allocation_data __user *data32, + struct ion_allocation_data __user *data) +{ + compat_size_t s; + compat_uint_t u; + compat_int_t i; + int err; + + err = get_user(s, &data32->len); + err |= put_user(s, &data->len); + err |= get_user(s, &data32->align); + err |= put_user(s, &data->align); + err |= get_user(u, &data32->heap_id_mask); + err |= put_user(u, &data->heap_id_mask); + err |= get_user(u, &data32->flags); + err |= put_user(u, &data->flags); + err |= get_user(i, &data32->handle); + err |= put_user(i, &data->handle); + + return err; +} + +static int compat_put_ion_allocation_data( + struct compat_ion_allocation_data __user *data32, + struct ion_allocation_data __user *data) +{ + compat_size_t s; + compat_uint_t u; + compat_int_t i; + int err; + + err = get_user(s, &data->len); + err |= put_user(s, &data32->len); + err |= get_user(s, &data->align); + err |= put_user(s, &data32->align); + err |= get_user(u, &data->heap_id_mask); + err |= put_user(u, &data32->heap_id_mask); + err |= get_user(u, &data->flags); + err |= put_user(u, &data32->flags); + err |= get_user(i, &data->handle); + err |= put_user(i, &data32->handle); + + return err; +} + +static int compat_get_ion_custom_data( + struct compat_ion_custom_data __user *data32, + struct ion_custom_data __user *data) +{ + compat_uint_t cmd; + compat_ulong_t arg; + int err; + + err = get_user(cmd, &data32->cmd); + err |= put_user(cmd, &data->cmd); + err |= get_user(arg, &data32->arg); + err |= put_user(arg, &data->arg); + + return err; +}; + +long compat_ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + long ret; + + if (!filp->f_op || !filp->f_op->unlocked_ioctl) + return -ENOTTY; + + switch (cmd) { + case ION_IOC_ALLOC: + { + struct compat_ion_allocation_data __user *data32; + struct ion_allocation_data __user *data; + int err; + + data32 = compat_ptr(arg); + data = compat_alloc_user_space(sizeof(*data)); + if (data == NULL) + return -EFAULT; + + err = compat_get_ion_allocation_data(data32, data); + if (err) + return err; + + ret = filp->f_op->unlocked_ioctl(filp, cmd, + (unsigned long)data); + err = compat_put_ion_allocation_data(data32, data); + return ret ? ret : err; + } + case ION_IOC_FREE: + { + struct compat_ion_allocation_data __user *data32; + struct ion_allocation_data __user *data; + int err; + + data32 = compat_ptr(arg); + data = compat_alloc_user_space(sizeof(*data)); + if (data == NULL) + return -EFAULT; + + err = compat_get_ion_allocation_data(data32, data); + if (err) + return err; + + return filp->f_op->unlocked_ioctl(filp, cmd, + (unsigned long)data); + } + case ION_IOC_CUSTOM: { + struct compat_ion_custom_data __user *data32; + struct ion_custom_data __user *data; + int err; + + data32 = compat_ptr(arg); + data = compat_alloc_user_space(sizeof(*data)); + if (data == NULL) + return -EFAULT; + + err = compat_get_ion_custom_data(data32, data); + if (err) + return err; + + return filp->f_op->unlocked_ioctl(filp, cmd, + (unsigned long)data); + } + case ION_IOC_SHARE: + case ION_IOC_MAP: + case ION_IOC_IMPORT: + case ION_IOC_SYNC: + return filp->f_op->unlocked_ioctl(filp, cmd, + (unsigned long)compat_ptr(arg)); + default: + return -ENOIOCTLCMD; + } +} diff --git a/drivers/gpu/ion/compat_ion.h b/drivers/gpu/ion/compat_ion.h new file mode 100644 index 00000000000..3a9c8c08c24 --- /dev/null +++ b/drivers/gpu/ion/compat_ion.h @@ -0,0 +1,30 @@ +/* + + * drivers/gpu/ion/compat_ion.h + * + * Copyright (C) 2013 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 _LINUX_COMPAT_ION_H +#define _LINUX_COMPAT_ION_H + +#if IS_ENABLED(CONFIG_COMPAT) + +long compat_ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); + +#else + +#define compat_ion_ioctl NULL + +#endif /* CONFIG_COMPAT */ +#endif /* _LINUX_COMPAT_ION_H */ diff --git a/drivers/gpu/ion/ion.c b/drivers/gpu/ion/ion.c index 1d4c8876ab5..e4ffc9d5b94 100644 --- a/drivers/gpu/ion/ion.c +++ b/drivers/gpu/ion/ion.c @@ -38,6 +38,7 @@ #include <linux/idr.h> #include "ion_priv.h" +#include "compat_ion.h" /** * struct ion_device - the metadata of the ion device node @@ -1139,7 +1140,7 @@ static long ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) if (IS_ERR(handle)) return PTR_ERR(handle); - data.handle = (struct ion_handle *)handle->id; + data.handle = handle->id; if (copy_to_user((void __user *)arg, &data, sizeof(data))) { ion_free(client, handle); @@ -1156,7 +1157,7 @@ static long ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) sizeof(struct ion_handle_data))) return -EFAULT; mutex_lock(&client->lock); - handle = ion_uhandle_get(client, (int)data.handle); + handle = ion_uhandle_get(client, data.handle); mutex_unlock(&client->lock); if (!handle) return -EINVAL; @@ -1171,7 +1172,7 @@ static long ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) if (copy_from_user(&data, (void __user *)arg, sizeof(data))) return -EFAULT; - handle = ion_uhandle_get(client, (int)data.handle); + handle = ion_uhandle_get(client, data.handle); data.fd = ion_share_dma_buf_fd(client, handle); if (copy_to_user((void __user *)arg, &data, sizeof(data))) return -EFAULT; @@ -1191,7 +1192,7 @@ static long ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) if (IS_ERR(handle)) ret = PTR_ERR(handle); else - data.handle = (struct ion_handle *)handle->id; + data.handle = handle->id; if (copy_to_user((void __user *)arg, &data, sizeof(struct ion_fd_data))) @@ -1256,6 +1257,7 @@ static const struct file_operations ion_fops = { .open = ion_open, .release = ion_release, .unlocked_ioctl = ion_ioctl, + .compat_ioctl = compat_ion_ioctl, }; static size_t ion_debug_heap_total(struct ion_client *client, diff --git a/drivers/staging/android/sw_sync.h b/drivers/staging/android/sw_sync.h index 585040be5f1..aba25cbb038 100644 --- a/drivers/staging/android/sw_sync.h +++ b/drivers/staging/android/sw_sync.h @@ -21,6 +21,7 @@ #ifdef __KERNEL__ +#include <linux/kconfig.h> #include "sync.h" struct sw_sync_timeline { @@ -35,10 +36,27 @@ struct sw_sync_pt { u32 value; }; +#if IS_ENABLED(CONFIG_SW_SYNC) struct sw_sync_timeline *sw_sync_timeline_create(const char *name); void sw_sync_timeline_inc(struct sw_sync_timeline *obj, u32 inc); struct sync_pt *sw_sync_pt_create(struct sw_sync_timeline *obj, u32 value); +#else +static inline struct sw_sync_timeline *sw_sync_timeline_create(const char *name) +{ + return NULL; +} + +static inline void sw_sync_timeline_inc(struct sw_sync_timeline *obj, u32 inc) +{ +} + +static inline struct sync_pt *sw_sync_pt_create(struct sw_sync_timeline *obj, + u32 value) +{ + return NULL; +} +#endif /* IS_ENABLED(CONFIG_SW_SYNC) */ #endif /* __KERNEL __ */ diff --git a/drivers/staging/android/sync.c b/drivers/staging/android/sync.c index 3893a357476..d38305b4093 100644 --- a/drivers/staging/android/sync.c +++ b/drivers/staging/android/sync.c @@ -79,27 +79,27 @@ static void sync_timeline_free(struct kref *kref) container_of(kref, struct sync_timeline, kref); unsigned long flags; - if (obj->ops->release_obj) - obj->ops->release_obj(obj); - spin_lock_irqsave(&sync_timeline_list_lock, flags); list_del(&obj->sync_timeline_list); spin_unlock_irqrestore(&sync_timeline_list_lock, flags); + if (obj->ops->release_obj) + obj->ops->release_obj(obj); + kfree(obj); } void sync_timeline_destroy(struct sync_timeline *obj) { obj->destroyed = true; + smp_wmb(); /* - * If this is not the last reference, signal any children - * that their parent is going away. + * signal any children that their parent is going away. */ + sync_timeline_signal(obj); - if (!kref_put(&obj->kref, sync_timeline_free)) - sync_timeline_signal(obj); + kref_put(&obj->kref, sync_timeline_free); } EXPORT_SYMBOL(sync_timeline_destroy); diff --git a/drivers/switch/switch_class.c b/drivers/switch/switch_class.c index e05fc259114..e373b625806 100644 --- a/drivers/switch/switch_class.c +++ b/drivers/switch/switch_class.c @@ -54,8 +54,8 @@ static ssize_t name_show(struct device *dev, struct device_attribute *attr, return sprintf(buf, "%s\n", sdev->name); } -static DEVICE_ATTR(state, S_IRUGO | S_IWUSR, state_show, NULL); -static DEVICE_ATTR(name, S_IRUGO | S_IWUSR, name_show, NULL); +static DEVICE_ATTR(state, S_IRUGO, state_show, NULL); +static DEVICE_ATTR(name, S_IRUGO, name_show, NULL); void switch_set_state(struct switch_dev *sdev, int state) { diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c index d0ec54b810c..04cbeb13481 100644 --- a/drivers/usb/gadget/android.c +++ b/drivers/usb/gadget/android.c @@ -1490,7 +1490,7 @@ static int __init init(void) err = usb_composite_probe(&android_usb_driver); if (err) { pr_err("%s: failed to probe driver %d", __func__, err); - goto err_create; + goto err_probe; } /* HACK: exchange composite's setup with ours */ @@ -1499,6 +1499,8 @@ static int __init init(void) return 0; +err_probe: + device_destroy(android_class, dev->dev->devt); err_create: kfree(dev); err_dev: diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index da4766b456f..7c5e27e52b9 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -2477,6 +2477,7 @@ source "drivers/video/omap2/Kconfig" source "drivers/video/exynos/Kconfig" source "drivers/video/mmp/Kconfig" source "drivers/video/backlight/Kconfig" +source "drivers/video/adf/Kconfig" if VT source "drivers/video/console/Kconfig" diff --git a/drivers/video/Makefile b/drivers/video/Makefile index e8bae8dd480..2babdef5fe0 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -12,6 +12,7 @@ fb-y := fbmem.o fbmon.o fbcmap.o fbsysfs.o \ modedb.o fbcvt.o fb-objs := $(fb-y) +obj-$(CONFIG_ADF) += adf/ obj-$(CONFIG_VT) += console/ obj-$(CONFIG_LOGO) += logo/ obj-y += backlight/ diff --git a/drivers/video/adf/Kconfig b/drivers/video/adf/Kconfig new file mode 100644 index 00000000000..33858b73d8b --- /dev/null +++ b/drivers/video/adf/Kconfig @@ -0,0 +1,14 @@ +menuconfig ADF + depends on SYNC + depends on DMA_SHARED_BUFFER + tristate "Atomic Display Framework" + +menuconfig ADF_FBDEV + depends on ADF + depends on FB + tristate "Helper for implementing the fbdev API in ADF drivers" + +menuconfig ADF_MEMBLOCK + depends on ADF + depends on HAVE_MEMBLOCK + tristate "Helper for using memblocks as buffers in ADF drivers" diff --git a/drivers/video/adf/Makefile b/drivers/video/adf/Makefile new file mode 100644 index 00000000000..78d0915122f --- /dev/null +++ b/drivers/video/adf/Makefile @@ -0,0 +1,15 @@ +ccflags-y := -Idrivers/staging/android + +CFLAGS_adf.o := -I$(src) + +obj-$(CONFIG_ADF) += adf.o \ + adf_client.o \ + adf_fops.o \ + adf_format.o \ + adf_sysfs.o + +obj-$(CONFIG_COMPAT) += adf_fops32.o + +obj-$(CONFIG_ADF_FBDEV) += adf_fbdev.o + +obj-$(CONFIG_ADF_MEMBLOCK) += adf_memblock.o diff --git a/drivers/video/adf/adf.c b/drivers/video/adf/adf.c new file mode 100644 index 00000000000..fd5bcde8785 --- /dev/null +++ b/drivers/video/adf/adf.c @@ -0,0 +1,1168 @@ +/* + * Copyright (C) 2013 Google, Inc. + * adf_modeinfo_{set_name,set_vrefresh} modified from + * drivers/gpu/drm/drm_modes.c + * + * 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/device.h> +#include <linux/idr.h> +#include <linux/highmem.h> +#include <linux/memblock.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <video/adf_format.h> + +#include "sw_sync.h" +#include "sync.h" + +#include "adf.h" +#include "adf_fops.h" +#include "adf_sysfs.h" + +#define CREATE_TRACE_POINTS +#include "adf_trace.h" + +#define ADF_SHORT_FENCE_TIMEOUT (1 * MSEC_PER_SEC) +#define ADF_LONG_FENCE_TIMEOUT (10 * MSEC_PER_SEC) + +static void adf_fence_wait(struct adf_device *dev, struct sync_fence *fence) +{ + /* sync_fence_wait() dumps debug information on timeout. Experience + has shown that if the pipeline gets stuck, a short timeout followed + by a longer one provides useful information for debugging. */ + int err = sync_fence_wait(fence, ADF_SHORT_FENCE_TIMEOUT); + if (err >= 0) + return; + + if (err == -ETIME) + err = sync_fence_wait(fence, ADF_LONG_FENCE_TIMEOUT); + + if (err < 0) + dev_warn(&dev->base.dev, "error waiting on fence: %d\n", err); +} + +void adf_buffer_cleanup(struct adf_buffer *buf) +{ + size_t i; + for (i = 0; i < ARRAY_SIZE(buf->dma_bufs); i++) + if (buf->dma_bufs[i]) + dma_buf_put(buf->dma_bufs[i]); + + if (buf->acquire_fence) + sync_fence_put(buf->acquire_fence); +} + +void adf_buffer_mapping_cleanup(struct adf_buffer_mapping *mapping, + struct adf_buffer *buf) +{ + /* calling adf_buffer_mapping_cleanup() is safe even if mapping is + uninitialized or partially-initialized, as long as it was + zeroed on allocation */ + size_t i; + for (i = 0; i < ARRAY_SIZE(mapping->sg_tables); i++) { + if (mapping->sg_tables[i]) + dma_buf_unmap_attachment(mapping->attachments[i], + mapping->sg_tables[i], DMA_TO_DEVICE); + if (mapping->attachments[i]) + dma_buf_detach(buf->dma_bufs[i], + mapping->attachments[i]); + } +} + +void adf_post_cleanup(struct adf_device *dev, struct adf_pending_post *post) +{ + size_t i; + + if (post->state) + dev->ops->state_free(dev, post->state); + + for (i = 0; i < post->config.n_bufs; i++) { + adf_buffer_mapping_cleanup(&post->config.mappings[i], + &post->config.bufs[i]); + adf_buffer_cleanup(&post->config.bufs[i]); + } + + kfree(post->config.custom_data); + kfree(post->config.mappings); + kfree(post->config.bufs); + kfree(post); +} + +static void adf_sw_advance_timeline(struct adf_device *dev) +{ +#ifdef CONFIG_SW_SYNC + sw_sync_timeline_inc(dev->timeline, 1); +#else + BUG(); +#endif +} + +static void adf_post_work_func(struct kthread_work *work) +{ + struct adf_device *dev = + container_of(work, struct adf_device, post_work); + struct adf_pending_post *post, *next; + struct list_head saved_list; + + mutex_lock(&dev->post_lock); + memcpy(&saved_list, &dev->post_list, sizeof(saved_list)); + list_replace_init(&dev->post_list, &saved_list); + mutex_unlock(&dev->post_lock); + + list_for_each_entry_safe(post, next, &saved_list, head) { + int i; + + for (i = 0; i < post->config.n_bufs; i++) { + struct sync_fence *fence = + post->config.bufs[i].acquire_fence; + if (fence) + adf_fence_wait(dev, fence); + } + + dev->ops->post(dev, &post->config, post->state); + + if (dev->ops->advance_timeline) + dev->ops->advance_timeline(dev, &post->config, + post->state); + else + adf_sw_advance_timeline(dev); + + list_del(&post->head); + if (dev->onscreen) + adf_post_cleanup(dev, dev->onscreen); + dev->onscreen = post; + } +} + +void adf_attachment_free(struct adf_attachment_list *attachment) +{ + list_del(&attachment->head); + kfree(attachment); +} + +struct adf_event_refcount *adf_obj_find_event_refcount(struct adf_obj *obj, + enum adf_event_type type) +{ + struct rb_root *root = &obj->event_refcount; + struct rb_node **new = &(root->rb_node); + struct rb_node *parent = NULL; + struct adf_event_refcount *refcount; + + while (*new) { + refcount = container_of(*new, struct adf_event_refcount, node); + parent = *new; + + if (refcount->type > type) + new = &(*new)->rb_left; + else if (refcount->type < type) + new = &(*new)->rb_right; + else + return refcount; + } + + refcount = kzalloc(sizeof(*refcount), GFP_KERNEL); + if (!refcount) + return NULL; + refcount->type = type; + + rb_link_node(&refcount->node, parent, new); + rb_insert_color(&refcount->node, root); + return refcount; +} + +/** + * adf_event_get - increase the refcount for an event + * + * @obj: the object that produces the event + * @type: the event type + * + * ADF will call the object's set_event() op if needed. ops are allowed + * to sleep, so adf_event_get() must NOT be called from an atomic context. + * + * Returns 0 if successful, or -%EINVAL if the object does not support the + * requested event type. + */ +int adf_event_get(struct adf_obj *obj, enum adf_event_type type) +{ + struct adf_event_refcount *refcount; + int old_refcount; + int ret; + + ret = adf_obj_check_supports_event(obj, type); + if (ret < 0) + return ret; + + mutex_lock(&obj->event_lock); + + refcount = adf_obj_find_event_refcount(obj, type); + if (!refcount) { + ret = -ENOMEM; + goto done; + } + + old_refcount = refcount->refcount++; + + if (old_refcount == 0) { + obj->ops->set_event(obj, type, true); + trace_adf_event_enable(obj, type); + } + +done: + mutex_unlock(&obj->event_lock); + return ret; +} +EXPORT_SYMBOL(adf_event_get); + +/** + * adf_event_put - decrease the refcount for an event + * + * @obj: the object that produces the event + * @type: the event type + * + * ADF will call the object's set_event() op if needed. ops are allowed + * to sleep, so adf_event_put() must NOT be called from an atomic context. + * + * Returns 0 if successful, -%EINVAL if the object does not support the + * requested event type, or -%EALREADY if the refcount is already 0. + */ +int adf_event_put(struct adf_obj *obj, enum adf_event_type type) +{ + struct adf_event_refcount *refcount; + int old_refcount; + int ret; + + ret = adf_obj_check_supports_event(obj, type); + if (ret < 0) + return ret; + + + mutex_lock(&obj->event_lock); + + refcount = adf_obj_find_event_refcount(obj, type); + if (!refcount) { + ret = -ENOMEM; + goto done; + } + + old_refcount = refcount->refcount--; + + if (WARN_ON(old_refcount == 0)) { + refcount->refcount++; + ret = -EALREADY; + } else if (old_refcount == 1) { + obj->ops->set_event(obj, type, false); + trace_adf_event_disable(obj, type); + } + +done: + mutex_unlock(&obj->event_lock); + return ret; +} +EXPORT_SYMBOL(adf_event_put); + +/** + * adf_vsync_wait - wait for a vsync event on a display interface + * + * @intf: the display interface + * @timeout: timeout in jiffies (0 = wait indefinitely) + * + * adf_vsync_wait() may sleep, so it must NOT be called from an atomic context. + * + * This function returns -%ERESTARTSYS if it is interrupted by a signal. + * If @timeout == 0 then this function returns 0 on vsync. If @timeout > 0 then + * this function returns the number of remaining jiffies or -%ETIMEDOUT on + * timeout. + */ +int adf_vsync_wait(struct adf_interface *intf, long timeout) +{ + ktime_t timestamp; + int ret; + unsigned long flags; + + read_lock_irqsave(&intf->vsync_lock, flags); + timestamp = intf->vsync_timestamp; + read_unlock_irqrestore(&intf->vsync_lock, flags); + + adf_vsync_get(intf); + if (timeout) { + ret = wait_event_interruptible_timeout(intf->vsync_wait, + !ktime_equal(timestamp, + intf->vsync_timestamp), + msecs_to_jiffies(timeout)); + if (ret == 0 && ktime_equal(timestamp, intf->vsync_timestamp)) + ret = -ETIMEDOUT; + } else { + ret = wait_event_interruptible(intf->vsync_wait, + !ktime_equal(timestamp, + intf->vsync_timestamp)); + } + adf_vsync_put(intf); + + return ret; +} +EXPORT_SYMBOL(adf_vsync_wait); + +static void adf_event_queue(struct adf_obj *obj, struct adf_event *event) +{ + struct adf_file *file; + unsigned long flags; + + trace_adf_event(obj, event->type); + + spin_lock_irqsave(&obj->file_lock, flags); + + list_for_each_entry(file, &obj->file_list, head) + if (test_bit(event->type, file->event_subscriptions)) + adf_file_queue_event(file, event); + + spin_unlock_irqrestore(&obj->file_lock, flags); +} + +/** + * adf_event_notify - notify userspace of a driver-private event + * + * @obj: the ADF object that produced the event + * @event: the event + * + * adf_event_notify() may be called safely from an atomic context. It will + * copy @event if needed, so @event may point to a variable on the stack. + * + * Drivers must NOT call adf_event_notify() for vsync and hotplug events. + * ADF provides adf_vsync_notify() and + * adf_hotplug_notify_{connected,disconnected}() for these events. + */ +int adf_event_notify(struct adf_obj *obj, struct adf_event *event) +{ + if (WARN_ON(event->type == ADF_EVENT_VSYNC || + event->type == ADF_EVENT_HOTPLUG)) + return -EINVAL; + + adf_event_queue(obj, event); + return 0; +} +EXPORT_SYMBOL(adf_event_notify); + +/** + * adf_vsync_notify - notify ADF of a display interface's vsync event + * + * @intf: the display interface + * @timestamp: the time the vsync occurred + * + * adf_vsync_notify() may be called safely from an atomic context. + */ +void adf_vsync_notify(struct adf_interface *intf, ktime_t timestamp) +{ + unsigned long flags; + struct adf_vsync_event event; + + write_lock_irqsave(&intf->vsync_lock, flags); + intf->vsync_timestamp = timestamp; + write_unlock_irqrestore(&intf->vsync_lock, flags); + + wake_up_interruptible_all(&intf->vsync_wait); + + event.base.type = ADF_EVENT_VSYNC; + event.base.length = sizeof(event); + event.timestamp = ktime_to_ns(timestamp); + adf_event_queue(&intf->base, &event.base); +} +EXPORT_SYMBOL(adf_vsync_notify); + +void adf_hotplug_notify(struct adf_interface *intf, bool connected, + struct drm_mode_modeinfo *modelist, size_t n_modes) +{ + unsigned long flags; + struct adf_hotplug_event event; + struct drm_mode_modeinfo *old_modelist; + + write_lock_irqsave(&intf->hotplug_modelist_lock, flags); + old_modelist = intf->modelist; + intf->hotplug_detect = connected; + intf->modelist = modelist; + intf->n_modes = n_modes; + write_unlock_irqrestore(&intf->hotplug_modelist_lock, flags); + + kfree(old_modelist); + + event.base.length = sizeof(event); + event.base.type = ADF_EVENT_HOTPLUG; + event.connected = connected; + adf_event_queue(&intf->base, &event.base); +} + +/** + * adf_hotplug_notify_connected - notify ADF of a display interface being + * connected to a display + * + * @intf: the display interface + * @modelist: hardware modes supported by display + * @n_modes: length of modelist + * + * @modelist is copied as needed, so it may point to a variable on the stack. + * + * adf_hotplug_notify_connected() may NOT be called safely from an atomic + * context. + * + * Returns 0 on success or error code (<0) on error. + */ +int adf_hotplug_notify_connected(struct adf_interface *intf, + struct drm_mode_modeinfo *modelist, size_t n_modes) +{ + struct drm_mode_modeinfo *modelist_copy; + + if (n_modes > ADF_MAX_MODES) + return -ENOMEM; + + modelist_copy = kzalloc(sizeof(modelist_copy[0]) * n_modes, + GFP_KERNEL); + if (!modelist_copy) + return -ENOMEM; + memcpy(modelist_copy, modelist, sizeof(modelist_copy[0]) * n_modes); + + adf_hotplug_notify(intf, true, modelist_copy, n_modes); + return 0; +} +EXPORT_SYMBOL(adf_hotplug_notify_connected); + +/** + * adf_hotplug_notify_disconnected - notify ADF of a display interface being + * disconnected from a display + * + * @intf: the display interface + * + * adf_hotplug_notify_disconnected() may be called safely from an atomic + * context. + */ +void adf_hotplug_notify_disconnected(struct adf_interface *intf) +{ + adf_hotplug_notify(intf, false, NULL, 0); +} +EXPORT_SYMBOL(adf_hotplug_notify_disconnected); + +static int adf_obj_init(struct adf_obj *obj, enum adf_obj_type type, + struct idr *idr, struct adf_device *parent, + const struct adf_obj_ops *ops, const char *fmt, va_list args) +{ + if (ops && ops->supports_event && !ops->set_event) { + pr_err("%s: %s implements supports_event but not set_event\n", + __func__, adf_obj_type_str(type)); + return -EINVAL; + } + + if (idr) { + int ret = idr_alloc(idr, obj, 0, 0, GFP_KERNEL); + if (ret < 0) { + pr_err("%s: allocating object id failed: %d\n", + __func__, ret); + return ret; + } + obj->id = ret; + } else { + obj->id = -1; + } + + vscnprintf(obj->name, sizeof(obj->name), fmt, args); + + obj->type = type; + obj->ops = ops; + obj->parent = parent; + mutex_init(&obj->event_lock); + obj->event_refcount = RB_ROOT; + spin_lock_init(&obj->file_lock); + INIT_LIST_HEAD(&obj->file_list); + return 0; +} + +static void adf_obj_destroy(struct adf_obj *obj, struct idr *idr) +{ + struct rb_node *node = rb_first(&obj->event_refcount); + + while (node) { + struct adf_event_refcount *refcount = + container_of(node, struct adf_event_refcount, + node); + kfree(refcount); + node = rb_first(&obj->event_refcount); + } + + mutex_destroy(&obj->event_lock); + if (idr) + idr_remove(idr, obj->id); +} + +/** + * adf_device_init - initialize ADF-internal data for a display device + * and create sysfs entries + * + * @dev: the display device + * @parent: the device's parent device + * @ops: the device's associated ops + * @fmt: formatting string for the display device's name + * + * @fmt specifies the device's sysfs filename and the name returned to + * userspace through the %ADF_GET_DEVICE_DATA ioctl. + * + * Returns 0 on success or error code (<0) on failure. + */ +int adf_device_init(struct adf_device *dev, struct device *parent, + const struct adf_device_ops *ops, const char *fmt, ...) +{ + int ret; + va_list args; + + if (!ops->validate || !ops->post) { + pr_err("%s: device must implement validate and post\n", + __func__); + return -EINVAL; + } + + if (!ops->complete_fence && !ops->advance_timeline) { + if (!IS_ENABLED(CONFIG_SW_SYNC)) { + pr_err("%s: device requires sw_sync but it is not enabled in the kernel\n", + __func__); + return -EINVAL; + } + } else if (!(ops->complete_fence && ops->advance_timeline)) { + pr_err("%s: device must implement both complete_fence and advance_timeline, or implement neither\n", + __func__); + return -EINVAL; + } + + memset(dev, 0, sizeof(*dev)); + + va_start(args, fmt); + ret = adf_obj_init(&dev->base, ADF_OBJ_DEVICE, NULL, dev, &ops->base, + fmt, args); + va_end(args); + if (ret < 0) + return ret; + + dev->dev = parent; + dev->ops = ops; + idr_init(&dev->overlay_engines); + idr_init(&dev->interfaces); + mutex_init(&dev->client_lock); + INIT_LIST_HEAD(&dev->post_list); + mutex_init(&dev->post_lock); + init_kthread_worker(&dev->post_worker); + INIT_LIST_HEAD(&dev->attached); + INIT_LIST_HEAD(&dev->attach_allowed); + + dev->post_thread = kthread_run(kthread_worker_fn, + &dev->post_worker, dev->base.name); + if (IS_ERR(dev->post_thread)) { + ret = PTR_ERR(dev->post_thread); + dev->post_thread = NULL; + + pr_err("%s: failed to run config posting thread: %d\n", + __func__, ret); + goto err; + } + init_kthread_work(&dev->post_work, adf_post_work_func); + + ret = adf_device_sysfs_init(dev); + if (ret < 0) + goto err; + + return 0; + +err: + adf_device_destroy(dev); + return ret; +} +EXPORT_SYMBOL(adf_device_init); + +/** + * adf_device_destroy - clean up ADF-internal data for a display device + * + * @dev: the display device + */ +void adf_device_destroy(struct adf_device *dev) +{ + struct adf_attachment_list *entry, *next; + + idr_destroy(&dev->interfaces); + idr_destroy(&dev->overlay_engines); + + if (dev->post_thread) { + flush_kthread_worker(&dev->post_worker); + kthread_stop(dev->post_thread); + } + + if (dev->onscreen) + adf_post_cleanup(dev, dev->onscreen); + adf_device_sysfs_destroy(dev); + list_for_each_entry_safe(entry, next, &dev->attach_allowed, head) { + adf_attachment_free(entry); + } + list_for_each_entry_safe(entry, next, &dev->attached, head) { + adf_attachment_free(entry); + } + mutex_destroy(&dev->post_lock); + mutex_destroy(&dev->client_lock); + adf_obj_destroy(&dev->base, NULL); +} +EXPORT_SYMBOL(adf_device_destroy); + +/** + * adf_interface_init - initialize ADF-internal data for a display interface + * and create sysfs entries + * + * @intf: the display interface + * @dev: the interface's "parent" display device + * @type: interface type (see enum @adf_interface_type) + * @idx: which interface of type @type; + * e.g. interface DSI.1 -> @type=%ADF_INTF_TYPE_DSI, @idx=1 + * @flags: informational flags (bitmask of %ADF_INTF_FLAG_* values) + * @ops: the interface's associated ops + * @fmt: formatting string for the display interface's name + * + * @dev must have previously been initialized with adf_device_init(). + * + * @fmt affects the name returned to userspace through the + * %ADF_GET_INTERFACE_DATA ioctl. It does not affect the sysfs filename, + * which is derived from @dev's name. + * + * Returns 0 on success or error code (<0) on failure. + */ +int adf_interface_init(struct adf_interface *intf, struct adf_device *dev, + enum adf_interface_type type, u32 idx, u32 flags, + const struct adf_interface_ops *ops, const char *fmt, ...) +{ + int ret; + va_list args; + const u32 allowed_flags = ADF_INTF_FLAG_PRIMARY | + ADF_INTF_FLAG_EXTERNAL; + + if (dev->n_interfaces == ADF_MAX_INTERFACES) { + pr_err("%s: parent device %s has too many interfaces\n", + __func__, dev->base.name); + return -ENOMEM; + } + + if (type >= ADF_INTF_MEMORY && type <= ADF_INTF_TYPE_DEVICE_CUSTOM) { + pr_err("%s: invalid interface type %u\n", __func__, type); + return -EINVAL; + } + + if (flags & ~allowed_flags) { + pr_err("%s: invalid interface flags 0x%X\n", __func__, + flags & ~allowed_flags); + return -EINVAL; + } + + memset(intf, 0, sizeof(*intf)); + + va_start(args, fmt); + ret = adf_obj_init(&intf->base, ADF_OBJ_INTERFACE, &dev->interfaces, + dev, ops ? &ops->base : NULL, fmt, args); + va_end(args); + if (ret < 0) + return ret; + + intf->type = type; + intf->idx = idx; + intf->flags = flags; + intf->ops = ops; + intf->dpms_state = DRM_MODE_DPMS_OFF; + init_waitqueue_head(&intf->vsync_wait); + rwlock_init(&intf->vsync_lock); + rwlock_init(&intf->hotplug_modelist_lock); + + ret = adf_interface_sysfs_init(intf); + if (ret < 0) + goto err; + dev->n_interfaces++; + + return 0; + +err: + adf_obj_destroy(&intf->base, &dev->interfaces); + return ret; +} +EXPORT_SYMBOL(adf_interface_init); + +/** + * adf_interface_destroy - clean up ADF-internal data for a display interface + * + * @intf: the display interface + */ +void adf_interface_destroy(struct adf_interface *intf) +{ + struct adf_device *dev = adf_interface_parent(intf); + struct adf_attachment_list *entry, *next; + + mutex_lock(&dev->client_lock); + list_for_each_entry_safe(entry, next, &dev->attach_allowed, head) { + if (entry->attachment.interface == intf) { + adf_attachment_free(entry); + dev->n_attach_allowed--; + } + } + list_for_each_entry_safe(entry, next, &dev->attached, head) { + if (entry->attachment.interface == intf) { + adf_device_detach_op(dev, + entry->attachment.overlay_engine, intf); + adf_attachment_free(entry); + dev->n_attached--; + } + } + kfree(intf->modelist); + adf_interface_sysfs_destroy(intf); + adf_obj_destroy(&intf->base, &dev->interfaces); + dev->n_interfaces--; + mutex_unlock(&dev->client_lock); +} +EXPORT_SYMBOL(adf_interface_destroy); + +static bool adf_overlay_engine_has_custom_formats( + const struct adf_overlay_engine_ops *ops) +{ + size_t i; + for (i = 0; i < ops->n_supported_formats; i++) + if (!adf_format_is_standard(ops->supported_formats[i])) + return true; + return false; +} + +/** + * adf_overlay_engine_init - initialize ADF-internal data for an + * overlay engine and create sysfs entries + * + * @eng: the overlay engine + * @dev: the overlay engine's "parent" display device + * @ops: the overlay engine's associated ops + * @fmt: formatting string for the overlay engine's name + * + * @dev must have previously been initialized with adf_device_init(). + * + * @fmt affects the name returned to userspace through the + * %ADF_GET_OVERLAY_ENGINE_DATA ioctl. It does not affect the sysfs filename, + * which is derived from @dev's name. + * + * Returns 0 on success or error code (<0) on failure. + */ +int adf_overlay_engine_init(struct adf_overlay_engine *eng, + struct adf_device *dev, + const struct adf_overlay_engine_ops *ops, const char *fmt, ...) +{ + int ret; + va_list args; + + if (!ops->supported_formats) { + pr_err("%s: overlay engine must support at least one format\n", + __func__); + return -EINVAL; + } + + if (ops->n_supported_formats > ADF_MAX_SUPPORTED_FORMATS) { + pr_err("%s: overlay engine supports too many formats\n", + __func__); + return -EINVAL; + } + + if (adf_overlay_engine_has_custom_formats(ops) && + !dev->ops->validate_custom_format) { + pr_err("%s: overlay engine has custom formats but parent device %s does not implement validate_custom_format\n", + __func__, dev->base.name); + return -EINVAL; + } + + memset(eng, 0, sizeof(*eng)); + + va_start(args, fmt); + ret = adf_obj_init(&eng->base, ADF_OBJ_OVERLAY_ENGINE, + &dev->overlay_engines, dev, &ops->base, fmt, args); + va_end(args); + if (ret < 0) + return ret; + + eng->ops = ops; + + ret = adf_overlay_engine_sysfs_init(eng); + if (ret < 0) + goto err; + + return 0; + +err: + adf_obj_destroy(&eng->base, &dev->overlay_engines); + return ret; +} +EXPORT_SYMBOL(adf_overlay_engine_init); + +/** + * adf_interface_destroy - clean up ADF-internal data for an overlay engine + * + * @eng: the overlay engine + */ +void adf_overlay_engine_destroy(struct adf_overlay_engine *eng) +{ + struct adf_device *dev = adf_overlay_engine_parent(eng); + struct adf_attachment_list *entry, *next; + + mutex_lock(&dev->client_lock); + list_for_each_entry_safe(entry, next, &dev->attach_allowed, head) { + if (entry->attachment.overlay_engine == eng) { + adf_attachment_free(entry); + dev->n_attach_allowed--; + } + } + list_for_each_entry_safe(entry, next, &dev->attached, head) { + if (entry->attachment.overlay_engine == eng) { + adf_device_detach_op(dev, eng, + entry->attachment.interface); + adf_attachment_free(entry); + dev->n_attached--; + } + } + adf_overlay_engine_sysfs_destroy(eng); + adf_obj_destroy(&eng->base, &dev->overlay_engines); + mutex_unlock(&dev->client_lock); +} +EXPORT_SYMBOL(adf_overlay_engine_destroy); + +struct adf_attachment_list *adf_attachment_find(struct list_head *list, + struct adf_overlay_engine *eng, struct adf_interface *intf) +{ + struct adf_attachment_list *entry; + list_for_each_entry(entry, list, head) { + if (entry->attachment.interface == intf && + entry->attachment.overlay_engine == eng) + return entry; + } + return NULL; +} + +int adf_attachment_validate(struct adf_device *dev, + struct adf_overlay_engine *eng, struct adf_interface *intf) +{ + struct adf_device *intf_dev = adf_interface_parent(intf); + struct adf_device *eng_dev = adf_overlay_engine_parent(eng); + + if (intf_dev != dev) { + dev_err(&dev->base.dev, "can't attach interface %s belonging to device %s\n", + intf->base.name, intf_dev->base.name); + return -EINVAL; + } + + if (eng_dev != dev) { + dev_err(&dev->base.dev, "can't attach overlay engine %s belonging to device %s\n", + eng->base.name, eng_dev->base.name); + return -EINVAL; + } + + return 0; +} + +/** + * adf_attachment_allow - add a new entry to the list of allowed + * attachments + * + * @dev: the parent device + * @eng: the overlay engine + * @intf: the interface + * + * adf_attachment_allow() indicates that the underlying display hardware allows + * @intf to scan out @eng's output. It is intended to be called at + * driver initialization for each supported overlay engine + interface pair. + * + * Returns 0 on success, -%EALREADY if the entry already exists, or -errno on + * any other failure. + */ +int adf_attachment_allow(struct adf_device *dev, + struct adf_overlay_engine *eng, struct adf_interface *intf) +{ + int ret; + struct adf_attachment_list *entry = NULL; + + ret = adf_attachment_validate(dev, eng, intf); + if (ret < 0) + return ret; + + mutex_lock(&dev->client_lock); + + if (dev->n_attach_allowed == ADF_MAX_ATTACHMENTS) { + ret = -ENOMEM; + goto done; + } + + if (adf_attachment_find(&dev->attach_allowed, eng, intf)) { + ret = -EALREADY; + goto done; + } + + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) { + ret = -ENOMEM; + goto done; + } + + entry->attachment.interface = intf; + entry->attachment.overlay_engine = eng; + list_add_tail(&entry->head, &dev->attach_allowed); + dev->n_attach_allowed++; + +done: + mutex_unlock(&dev->client_lock); + if (ret < 0) + kfree(entry); + + return ret; +} + +/** + * adf_obj_type_str - string representation of an adf_obj_type + * + * @type: the object type + */ +const char *adf_obj_type_str(enum adf_obj_type type) +{ + switch (type) { + case ADF_OBJ_OVERLAY_ENGINE: + return "overlay engine"; + + case ADF_OBJ_INTERFACE: + return "interface"; + + case ADF_OBJ_DEVICE: + return "device"; + + default: + return "unknown"; + } +} +EXPORT_SYMBOL(adf_obj_type_str); + +/** + * adf_interface_type_str - string representation of an adf_interface's type + * + * @intf: the interface + */ +const char *adf_interface_type_str(struct adf_interface *intf) +{ + switch (intf->type) { + case ADF_INTF_DSI: + return "DSI"; + + case ADF_INTF_eDP: + return "eDP"; + + case ADF_INTF_DPI: + return "DPI"; + + case ADF_INTF_VGA: + return "VGA"; + + case ADF_INTF_DVI: + return "DVI"; + + case ADF_INTF_HDMI: + return "HDMI"; + + case ADF_INTF_MEMORY: + return "memory"; + + default: + if (intf->type >= ADF_INTF_TYPE_DEVICE_CUSTOM) { + if (intf->ops && intf->ops->type_str) + return intf->ops->type_str(intf); + return "custom"; + } + return "unknown"; + } +} +EXPORT_SYMBOL(adf_interface_type_str); + +/** + * adf_event_type_str - string representation of an adf_event_type + * + * @obj: ADF object that produced the event + * @type: event type + */ +const char *adf_event_type_str(struct adf_obj *obj, enum adf_event_type type) +{ + switch (type) { + case ADF_EVENT_VSYNC: + return "vsync"; + + case ADF_EVENT_HOTPLUG: + return "hotplug"; + + default: + if (type >= ADF_EVENT_DEVICE_CUSTOM) { + if (obj->ops && obj->ops->event_type_str) + return obj->ops->event_type_str(obj, type); + return "custom"; + } + return "unknown"; + } +} +EXPORT_SYMBOL(adf_event_type_str); + +/** + * adf_format_str - string representation of an ADF/DRM fourcc format + * + * @format: format fourcc + * @buf: target buffer for the format's string representation + */ +void adf_format_str(u32 format, char buf[ADF_FORMAT_STR_SIZE]) +{ + buf[0] = format & 0xFF; + buf[1] = (format >> 8) & 0xFF; + buf[2] = (format >> 16) & 0xFF; + buf[3] = (format >> 24) & 0xFF; + buf[4] = '\0'; +} +EXPORT_SYMBOL(adf_format_str); + +/** + * adf_format_validate_yuv - validate the number and size of planes in buffers + * with a custom YUV format. + * + * @dev: ADF device performing the validation + * @buf: buffer to validate + * @num_planes: expected number of planes + * @hsub: expected horizontal chroma subsampling factor, in pixels + * @vsub: expected vertical chroma subsampling factor, in pixels + * @cpp: expected bytes per pixel for each plane (length @num_planes) + * + * adf_format_validate_yuv() is intended to be called as a helper from @dev's + * validate_custom_format() op. + * + * Returns 0 if @buf has the expected number of planes and each plane + * has sufficient size, or -EINVAL otherwise. + */ +int adf_format_validate_yuv(struct adf_device *dev, struct adf_buffer *buf, + u8 num_planes, u8 hsub, u8 vsub, u8 cpp[]) +{ + u8 i; + + if (num_planes != buf->n_planes) { + char format_str[ADF_FORMAT_STR_SIZE]; + adf_format_str(buf->format, format_str); + dev_err(&dev->base.dev, "%u planes expected for format %s but %u planes provided\n", + num_planes, format_str, buf->n_planes); + return -EINVAL; + } + + if (buf->w == 0 || buf->w % hsub) { + dev_err(&dev->base.dev, "bad buffer width %u\n", buf->w); + return -EINVAL; + } + + if (buf->h == 0 || buf->h % vsub) { + dev_err(&dev->base.dev, "bad buffer height %u\n", buf->h); + return -EINVAL; + } + + for (i = 0; i < num_planes; i++) { + u32 width = buf->w / (i != 0 ? hsub : 1); + u32 height = buf->h / (i != 0 ? vsub : 1); + u8 cpp = adf_format_plane_cpp(buf->format, i); + + if (buf->pitch[i] < (u64) width * cpp) { + dev_err(&dev->base.dev, "plane %u pitch is shorter than buffer width (pitch = %u, width = %u, bpp = %u)\n", + i, buf->pitch[i], width, cpp * 8); + return -EINVAL; + } + + if ((u64) height * buf->pitch[i] + buf->offset[i] > + buf->dma_bufs[i]->size) { + dev_err(&dev->base.dev, "plane %u buffer too small (height = %u, pitch = %u, offset = %u, size = %zu)\n", + i, height, buf->pitch[i], + buf->offset[i], buf->dma_bufs[i]->size); + return -EINVAL; + } + } + + return 0; +} +EXPORT_SYMBOL(adf_format_validate_yuv); + +/** + * adf_modeinfo_set_name - sets the name of a mode from its display resolution + * + * @mode: mode + * + * adf_modeinfo_set_name() fills in @mode->name in the format + * "[hdisplay]x[vdisplay](i)". It is intended to help drivers create + * ADF/DRM-style modelists from other mode formats. + */ +void adf_modeinfo_set_name(struct drm_mode_modeinfo *mode) +{ + bool interlaced = mode->flags & DRM_MODE_FLAG_INTERLACE; + + snprintf(mode->name, DRM_DISPLAY_MODE_LEN, "%dx%d%s", + mode->hdisplay, mode->vdisplay, + interlaced ? "i" : ""); +} +EXPORT_SYMBOL(adf_modeinfo_set_name); + +/** + * adf_modeinfo_set_vrefresh - sets the vrefresh of a mode from its other + * timing data + * + * @mode: mode + * + * adf_modeinfo_set_vrefresh() calculates @mode->vrefresh from + * @mode->{h,v}display and @mode->flags. It is intended to help drivers + * create ADF/DRM-style modelists from other mode formats. + */ +void adf_modeinfo_set_vrefresh(struct drm_mode_modeinfo *mode) +{ + int refresh = 0; + unsigned int calc_val; + + if (mode->vrefresh > 0) + return; + + if (mode->htotal <= 0 || mode->vtotal <= 0) + return; + + /* work out vrefresh the value will be x1000 */ + calc_val = (mode->clock * 1000); + calc_val /= mode->htotal; + refresh = (calc_val + mode->vtotal / 2) / mode->vtotal; + + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + refresh *= 2; + if (mode->flags & DRM_MODE_FLAG_DBLSCAN) + refresh /= 2; + if (mode->vscan > 1) + refresh /= mode->vscan; + + mode->vrefresh = refresh; +} +EXPORT_SYMBOL(adf_modeinfo_set_vrefresh); + +static int __init adf_init(void) +{ + int err; + + err = adf_sysfs_init(); + if (err < 0) + return err; + + return 0; +} + +static void __exit adf_exit(void) +{ + adf_sysfs_destroy(); +} + +module_init(adf_init); +module_exit(adf_exit); diff --git a/drivers/video/adf/adf.h b/drivers/video/adf/adf.h new file mode 100644 index 00000000000..3bcf1fabc23 --- /dev/null +++ b/drivers/video/adf/adf.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2013 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 __VIDEO_ADF_ADF_H +#define __VIDEO_ADF_ADF_H + +#include <linux/idr.h> +#include <linux/list.h> +#include <video/adf.h> +#include "sync.h" + +struct adf_event_refcount { + struct rb_node node; + enum adf_event_type type; + int refcount; +}; + +void adf_buffer_cleanup(struct adf_buffer *buf); +void adf_buffer_mapping_cleanup(struct adf_buffer_mapping *mapping, + struct adf_buffer *buf); +void adf_post_cleanup(struct adf_device *dev, struct adf_pending_post *post); + +struct adf_attachment_list *adf_attachment_find(struct list_head *list, + struct adf_overlay_engine *eng, struct adf_interface *intf); +int adf_attachment_validate(struct adf_device *dev, + struct adf_overlay_engine *eng, struct adf_interface *intf); +void adf_attachment_free(struct adf_attachment_list *attachment); + +struct adf_event_refcount *adf_obj_find_event_refcount(struct adf_obj *obj, + enum adf_event_type type); + +static inline int adf_obj_check_supports_event(struct adf_obj *obj, + enum adf_event_type type) +{ + if (!obj->ops || !obj->ops->supports_event) + return -EOPNOTSUPP; + if (!obj->ops->supports_event(obj, type)) + return -EINVAL; + return 0; +} + +static inline int adf_device_attach_op(struct adf_device *dev, + struct adf_overlay_engine *eng, struct adf_interface *intf) +{ + if (!dev->ops->attach) + return 0; + + return dev->ops->attach(dev, eng, intf); +} + +static inline int adf_device_detach_op(struct adf_device *dev, + struct adf_overlay_engine *eng, struct adf_interface *intf) +{ + if (!dev->ops->detach) + return 0; + + return dev->ops->detach(dev, eng, intf); +} + +#endif /* __VIDEO_ADF_ADF_H */ diff --git a/drivers/video/adf/adf_client.c b/drivers/video/adf/adf_client.c new file mode 100644 index 00000000000..e4a79213507 --- /dev/null +++ b/drivers/video/adf/adf_client.c @@ -0,0 +1,807 @@ +/* + * Copyright (C) 2013 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/kthread.h> +#include <linux/mutex.h> +#include <linux/slab.h> + +#include "sw_sync.h" + +#include <video/adf.h> +#include <video/adf_client.h> +#include <video/adf_format.h> + +#include "adf.h" + +static inline bool vsync_active(u8 state) +{ + return state == DRM_MODE_DPMS_ON || state == DRM_MODE_DPMS_STANDBY; +} + +/** + * adf_interface_blank - set interface's DPMS state + * + * @intf: the interface + * @state: one of %DRM_MODE_DPMS_* + * + * Returns 0 on success or -errno on failure. + */ +int adf_interface_blank(struct adf_interface *intf, u8 state) +{ + struct adf_device *dev = adf_interface_parent(intf); + u8 prev_state; + bool disable_vsync; + bool enable_vsync; + int ret = 0; + struct adf_event_refcount *vsync_refcount; + + if (!intf->ops || !intf->ops->blank) + return -EOPNOTSUPP; + + mutex_lock(&dev->client_lock); + if (state != DRM_MODE_DPMS_ON) + flush_kthread_worker(&dev->post_worker); + mutex_lock(&intf->base.event_lock); + + vsync_refcount = adf_obj_find_event_refcount(&intf->base, + ADF_EVENT_VSYNC); + if (!vsync_refcount) { + ret = -ENOMEM; + goto done; + } + + prev_state = intf->dpms_state; + if (prev_state == state) { + ret = -EBUSY; + goto done; + } + + disable_vsync = vsync_active(prev_state) && + !vsync_active(state) && + vsync_refcount->refcount; + enable_vsync = !vsync_active(prev_state) && + vsync_active(state) && + vsync_refcount->refcount; + + if (disable_vsync) + intf->base.ops->set_event(&intf->base, ADF_EVENT_VSYNC, + false); + + ret = intf->ops->blank(intf, state); + if (ret < 0) { + if (disable_vsync) + intf->base.ops->set_event(&intf->base, ADF_EVENT_VSYNC, + true); + goto done; + } + + if (enable_vsync) + intf->base.ops->set_event(&intf->base, ADF_EVENT_VSYNC, + true); + + intf->dpms_state = state; +done: + mutex_unlock(&intf->base.event_lock); + mutex_unlock(&dev->client_lock); + return ret; +} +EXPORT_SYMBOL(adf_interface_blank); + +/** + * adf_interface_blank - get interface's current DPMS state + * + * @intf: the interface + * + * Returns one of %DRM_MODE_DPMS_*. + */ +u8 adf_interface_dpms_state(struct adf_interface *intf) +{ + struct adf_device *dev = adf_interface_parent(intf); + u8 dpms_state; + + mutex_lock(&dev->client_lock); + dpms_state = intf->dpms_state; + mutex_unlock(&dev->client_lock); + + return dpms_state; +} +EXPORT_SYMBOL(adf_interface_dpms_state); + +/** + * adf_interface_current_mode - get interface's current display mode + * + * @intf: the interface + * @mode: returns the current mode + */ +void adf_interface_current_mode(struct adf_interface *intf, + struct drm_mode_modeinfo *mode) +{ + struct adf_device *dev = adf_interface_parent(intf); + + mutex_lock(&dev->client_lock); + memcpy(mode, &intf->current_mode, sizeof(*mode)); + mutex_unlock(&dev->client_lock); +} +EXPORT_SYMBOL(adf_interface_current_mode); + +/** + * adf_interface_modelist - get interface's modelist + * + * @intf: the interface + * @modelist: storage for the modelist (optional) + * @n_modes: length of @modelist + * + * If @modelist is not NULL, adf_interface_modelist() will copy up to @n_modes + * modelist entries into @modelist. + * + * Returns the length of the modelist. + */ +size_t adf_interface_modelist(struct adf_interface *intf, + struct drm_mode_modeinfo *modelist, size_t n_modes) +{ + unsigned long flags; + size_t retval; + + read_lock_irqsave(&intf->hotplug_modelist_lock, flags); + if (modelist) + memcpy(modelist, intf->modelist, sizeof(modelist[0]) * + min(n_modes, intf->n_modes)); + retval = intf->n_modes; + read_unlock_irqrestore(&intf->hotplug_modelist_lock, flags); + + return retval; +} +EXPORT_SYMBOL(adf_interface_modelist); + +/** + * adf_interface_set_mode - set interface's display mode + * + * @intf: the interface + * @mode: the new mode + * + * Returns 0 on success or -errno on failure. + */ +int adf_interface_set_mode(struct adf_interface *intf, + struct drm_mode_modeinfo *mode) +{ + struct adf_device *dev = adf_interface_parent(intf); + int ret = 0; + + if (!intf->ops || !intf->ops->modeset) + return -EOPNOTSUPP; + + mutex_lock(&dev->client_lock); + flush_kthread_worker(&dev->post_worker); + + ret = intf->ops->modeset(intf, mode); + if (ret < 0) + goto done; + + memcpy(&intf->current_mode, mode, sizeof(*mode)); +done: + mutex_unlock(&dev->client_lock); + return ret; +} +EXPORT_SYMBOL(adf_interface_set_mode); + +/** + * adf_interface_screen_size - get size of screen connected to interface + * + * @intf: the interface + * @width_mm: returns the screen width in mm + * @height_mm: returns the screen width in mm + * + * Returns 0 on success or -errno on failure. + */ +int adf_interface_get_screen_size(struct adf_interface *intf, u16 *width_mm, + u16 *height_mm) +{ + struct adf_device *dev = adf_interface_parent(intf); + int ret; + + if (!intf->ops || !intf->ops->screen_size) + return -EOPNOTSUPP; + + mutex_lock(&dev->client_lock); + ret = intf->ops->screen_size(intf, width_mm, height_mm); + mutex_unlock(&dev->client_lock); + + return ret; +} +EXPORT_SYMBOL(adf_interface_get_screen_size); + +/** + * adf_overlay_engine_supports_format - returns whether a format is in an + * overlay engine's supported list + * + * @eng: the overlay engine + * @format: format fourcc + */ +bool adf_overlay_engine_supports_format(struct adf_overlay_engine *eng, + u32 format) +{ + size_t i; + for (i = 0; i < eng->ops->n_supported_formats; i++) + if (format == eng->ops->supported_formats[i]) + return true; + + return false; +} +EXPORT_SYMBOL(adf_overlay_engine_supports_format); + +static int adf_buffer_validate(struct adf_buffer *buf) +{ + struct adf_overlay_engine *eng = buf->overlay_engine; + struct device *dev = &eng->base.dev; + struct adf_device *parent = adf_overlay_engine_parent(eng); + u8 hsub, vsub, num_planes, cpp[ADF_MAX_PLANES], i; + + if (!adf_overlay_engine_supports_format(eng, buf->format)) { + char format_str[ADF_FORMAT_STR_SIZE]; + adf_format_str(buf->format, format_str); + dev_err(dev, "unsupported format %s\n", format_str); + return -EINVAL; + } + + if (!adf_format_is_standard(buf->format)) + return parent->ops->validate_custom_format(parent, buf); + + hsub = adf_format_horz_chroma_subsampling(buf->format); + vsub = adf_format_vert_chroma_subsampling(buf->format); + num_planes = adf_format_num_planes(buf->format); + for (i = 0; i < num_planes; i++) + cpp[i] = adf_format_plane_cpp(buf->format, i); + + return adf_format_validate_yuv(parent, buf, num_planes, hsub, vsub, + cpp); +} + +static int adf_buffer_map(struct adf_device *dev, struct adf_buffer *buf, + struct adf_buffer_mapping *mapping) +{ + int ret = 0; + size_t i; + + for (i = 0; i < buf->n_planes; i++) { + struct dma_buf_attachment *attachment; + struct sg_table *sg_table; + + attachment = dma_buf_attach(buf->dma_bufs[i], dev->dev); + if (IS_ERR(attachment)) { + ret = PTR_ERR(attachment); + dev_err(&dev->base.dev, "attaching plane %u failed: %d\n", + i, ret); + goto done; + } + mapping->attachments[i] = attachment; + + sg_table = dma_buf_map_attachment(attachment, DMA_TO_DEVICE); + if (IS_ERR(sg_table)) { + ret = PTR_ERR(sg_table); + dev_err(&dev->base.dev, "mapping plane %u failed: %d", + i, ret); + goto done; + } else if (!sg_table) { + ret = -ENOMEM; + dev_err(&dev->base.dev, "mapping plane %u failed\n", i); + goto done; + } + mapping->sg_tables[i] = sg_table; + } + +done: + if (ret < 0) + adf_buffer_mapping_cleanup(mapping, buf); + + return ret; +} + +static struct sync_fence *adf_sw_complete_fence(struct adf_device *dev) +{ + struct sync_pt *pt; + struct sync_fence *complete_fence; + + if (!dev->timeline) { + dev->timeline = sw_sync_timeline_create(dev->base.name); + if (!dev->timeline) + return ERR_PTR(-ENOMEM); + dev->timeline_max = 1; + } + + dev->timeline_max++; + pt = sw_sync_pt_create(dev->timeline, dev->timeline_max); + if (!pt) + goto err_pt_create; + complete_fence = sync_fence_create(dev->base.name, pt); + if (!complete_fence) + goto err_fence_create; + + return complete_fence; + +err_fence_create: + sync_pt_free(pt); +err_pt_create: + dev->timeline_max--; + return ERR_PTR(-ENOSYS); +} + +/** + * adf_device_post - flip to a new set of buffers + * + * @dev: device targeted by the flip + * @intfs: interfaces targeted by the flip + * @n_intfs: number of targeted interfaces + * @bufs: description of buffers displayed + * @n_bufs: number of buffers displayed + * @custom_data: driver-private data + * @custom_data_size: size of driver-private data + * + * adf_device_post() will copy @intfs, @bufs, and @custom_data, so they may + * point to variables on the stack. adf_device_post() also takes its own + * reference on each of the dma-bufs in @bufs. The adf_device_post_nocopy() + * variant transfers ownership of these resources to ADF instead. + * + * On success, returns a sync fence which signals when the buffers are removed + * from the screen. On failure, returns ERR_PTR(-errno). + */ +struct sync_fence *adf_device_post(struct adf_device *dev, + struct adf_interface **intfs, size_t n_intfs, + struct adf_buffer *bufs, size_t n_bufs, void *custom_data, + size_t custom_data_size) +{ + struct adf_interface **intfs_copy = NULL; + struct adf_buffer *bufs_copy = NULL; + void *custom_data_copy = NULL; + struct sync_fence *ret; + size_t i; + + intfs_copy = kzalloc(sizeof(intfs_copy[0]) * n_intfs, GFP_KERNEL); + if (!intfs_copy) + return ERR_PTR(-ENOMEM); + + bufs_copy = kzalloc(sizeof(bufs_copy[0]) * n_bufs, GFP_KERNEL); + if (!bufs_copy) { + ret = ERR_PTR(-ENOMEM); + goto err_alloc; + } + + custom_data_copy = kzalloc(custom_data_size, GFP_KERNEL); + if (!custom_data_copy) { + ret = ERR_PTR(-ENOMEM); + goto err_alloc; + } + + for (i = 0; i < n_bufs; i++) { + size_t j; + for (j = 0; j < bufs[i].n_planes; j++) + get_dma_buf(bufs[i].dma_bufs[j]); + } + + memcpy(intfs_copy, intfs, sizeof(intfs_copy[0]) * n_intfs); + memcpy(bufs_copy, bufs, sizeof(bufs_copy[0]) * n_bufs); + memcpy(custom_data_copy, custom_data, custom_data_size); + + ret = adf_device_post_nocopy(dev, intfs_copy, n_intfs, bufs_copy, + n_bufs, custom_data_copy, custom_data_size); + if (IS_ERR(ret)) + goto err_post; + + return ret; + +err_post: + for (i = 0; i < n_bufs; i++) { + size_t j; + for (j = 0; j < bufs[i].n_planes; j++) + dma_buf_put(bufs[i].dma_bufs[j]); + } +err_alloc: + kfree(custom_data_copy); + kfree(bufs_copy); + kfree(intfs_copy); + return ret; +} +EXPORT_SYMBOL(adf_device_post); + +/** + * adf_device_post_nocopy - flip to a new set of buffers + * + * adf_device_post_nocopy() has the same behavior as adf_device_post(), + * except ADF does not copy @intfs, @bufs, or @custom_data, and it does + * not take an extra reference on the dma-bufs in @bufs. + * + * @intfs, @bufs, and @custom_data must point to buffers allocated by + * kmalloc(). On success, ADF takes ownership of these buffers and the dma-bufs + * in @bufs, and will kfree()/dma_buf_put() them when they are no longer needed. + * On failure, adf_device_post_nocopy() does NOT take ownership of these + * buffers or the dma-bufs, and the caller must clean them up. + * + * adf_device_post_nocopy() is mainly intended for implementing ADF's ioctls. + * Clients may find the nocopy variant useful in limited cases, but most should + * call adf_device_post() instead. + */ +struct sync_fence *adf_device_post_nocopy(struct adf_device *dev, + struct adf_interface **intfs, size_t n_intfs, + struct adf_buffer *bufs, size_t n_bufs, + void *custom_data, size_t custom_data_size) +{ + struct adf_pending_post *cfg; + struct adf_buffer_mapping *mappings; + struct sync_fence *ret; + size_t i; + int err; + + cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); + if (!cfg) + return ERR_PTR(-ENOMEM); + + mappings = kzalloc(sizeof(mappings[0]) * n_bufs, GFP_KERNEL); + if (!mappings) { + ret = ERR_PTR(-ENOMEM); + goto err_alloc; + } + + mutex_lock(&dev->client_lock); + + for (i = 0; i < n_bufs; i++) { + err = adf_buffer_validate(&bufs[i]); + if (err < 0) { + ret = ERR_PTR(err); + goto err_buf; + } + + err = adf_buffer_map(dev, &bufs[i], &mappings[i]); + if (err < 0) { + ret = ERR_PTR(err); + goto err_buf; + } + } + + INIT_LIST_HEAD(&cfg->head); + cfg->config.n_bufs = n_bufs; + cfg->config.bufs = bufs; + cfg->config.mappings = mappings; + cfg->config.custom_data = custom_data; + cfg->config.custom_data_size = custom_data_size; + + err = dev->ops->validate(dev, &cfg->config, &cfg->state); + if (err < 0) { + ret = ERR_PTR(err); + goto err_buf; + } + + mutex_lock(&dev->post_lock); + + if (dev->ops->complete_fence) + ret = dev->ops->complete_fence(dev, &cfg->config, + cfg->state); + else + ret = adf_sw_complete_fence(dev); + + if (IS_ERR(ret)) + goto err_fence; + + list_add_tail(&cfg->head, &dev->post_list); + queue_kthread_work(&dev->post_worker, &dev->post_work); + mutex_unlock(&dev->post_lock); + mutex_unlock(&dev->client_lock); + kfree(intfs); + return ret; + +err_fence: + mutex_unlock(&dev->post_lock); + +err_buf: + for (i = 0; i < n_bufs; i++) + adf_buffer_mapping_cleanup(&mappings[i], &bufs[i]); + + mutex_unlock(&dev->client_lock); + kfree(mappings); + +err_alloc: + kfree(cfg); + return ret; +} +EXPORT_SYMBOL(adf_device_post_nocopy); + +static void adf_attachment_list_to_array(struct adf_device *dev, + struct list_head *src, struct adf_attachment *dst, size_t size) +{ + struct adf_attachment_list *entry; + size_t i = 0; + + if (!dst) + return; + + list_for_each_entry(entry, src, head) { + if (i == size) + return; + dst[i] = entry->attachment; + i++; + } +} + +/** + * adf_device_attachments - get device's list of active attachments + * + * @dev: the device + * @attachments: storage for the attachment list (optional) + * @n_attachments: length of @attachments + * + * If @attachments is not NULL, adf_device_attachments() will copy up to + * @n_attachments entries into @attachments. + * + * Returns the length of the active attachment list. + */ +size_t adf_device_attachments(struct adf_device *dev, + struct adf_attachment *attachments, size_t n_attachments) +{ + size_t retval; + + mutex_lock(&dev->client_lock); + adf_attachment_list_to_array(dev, &dev->attached, attachments, + n_attachments); + retval = dev->n_attached; + mutex_unlock(&dev->client_lock); + + return retval; +} +EXPORT_SYMBOL(adf_device_attachments); + +/** + * adf_device_attachments_allowed - get device's list of allowed attachments + * + * @dev: the device + * @attachments: storage for the attachment list (optional) + * @n_attachments: length of @attachments + * + * If @attachments is not NULL, adf_device_attachments_allowed() will copy up to + * @n_attachments entries into @attachments. + * + * Returns the length of the allowed attachment list. + */ +size_t adf_device_attachments_allowed(struct adf_device *dev, + struct adf_attachment *attachments, size_t n_attachments) +{ + size_t retval; + + mutex_lock(&dev->client_lock); + adf_attachment_list_to_array(dev, &dev->attach_allowed, attachments, + n_attachments); + retval = dev->n_attach_allowed; + mutex_unlock(&dev->client_lock); + + return retval; +} +EXPORT_SYMBOL(adf_device_attachments_allowed); + +/** + * adf_device_attached - return whether an overlay engine and interface are + * attached + * + * @dev: the parent device + * @eng: the overlay engine + * @intf: the interface + */ +bool adf_device_attached(struct adf_device *dev, struct adf_overlay_engine *eng, + struct adf_interface *intf) +{ + struct adf_attachment_list *attachment; + + mutex_lock(&dev->client_lock); + attachment = adf_attachment_find(&dev->attached, eng, intf); + mutex_unlock(&dev->client_lock); + + return attachment != NULL; +} +EXPORT_SYMBOL(adf_device_attached); + +/** + * adf_device_attach_allowed - return whether the ADF device supports attaching + * an overlay engine and interface + * + * @dev: the parent device + * @eng: the overlay engine + * @intf: the interface + */ +bool adf_device_attach_allowed(struct adf_device *dev, + struct adf_overlay_engine *eng, struct adf_interface *intf) +{ + struct adf_attachment_list *attachment; + + mutex_lock(&dev->client_lock); + attachment = adf_attachment_find(&dev->attach_allowed, eng, intf); + mutex_unlock(&dev->client_lock); + + return attachment != NULL; +} +EXPORT_SYMBOL(adf_device_attach_allowed); +/** + * adf_device_attach - attach an overlay engine to an interface + * + * @dev: the parent device + * @eng: the overlay engine + * @intf: the interface + * + * Returns 0 on success, -%EINVAL if attaching @intf and @eng is not allowed, + * -%EALREADY if @intf and @eng are already attached, or -errno on any other + * failure. + */ +int adf_device_attach(struct adf_device *dev, struct adf_overlay_engine *eng, + struct adf_interface *intf) +{ + int ret; + struct adf_attachment_list *attachment = NULL; + + ret = adf_attachment_validate(dev, eng, intf); + if (ret < 0) + return ret; + + mutex_lock(&dev->client_lock); + + if (dev->n_attached == ADF_MAX_ATTACHMENTS) { + ret = -ENOMEM; + goto done; + } + + if (!adf_attachment_find(&dev->attach_allowed, eng, intf)) { + ret = -EINVAL; + goto done; + } + + if (adf_attachment_find(&dev->attached, eng, intf)) { + ret = -EALREADY; + goto done; + } + + ret = adf_device_attach_op(dev, eng, intf); + if (ret < 0) + goto done; + + attachment = kzalloc(sizeof(*attachment), GFP_KERNEL); + if (!attachment) { + ret = -ENOMEM; + goto done; + } + + attachment->attachment.interface = intf; + attachment->attachment.overlay_engine = eng; + list_add_tail(&attachment->head, &dev->attached); + dev->n_attached++; + +done: + mutex_unlock(&dev->client_lock); + if (ret < 0) + kfree(attachment); + + return ret; +} +EXPORT_SYMBOL(adf_device_attach); + +/** + * adf_device_detach - detach an overlay engine from an interface + * + * @dev: the parent device + * @eng: the overlay engine + * @intf: the interface + * + * Returns 0 on success, -%EINVAL if @intf and @eng are not attached, + * or -errno on any other failure. + */ +int adf_device_detach(struct adf_device *dev, struct adf_overlay_engine *eng, + struct adf_interface *intf) +{ + int ret; + struct adf_attachment_list *attachment; + + ret = adf_attachment_validate(dev, eng, intf); + if (ret < 0) + return ret; + + mutex_lock(&dev->client_lock); + + attachment = adf_attachment_find(&dev->attached, eng, intf); + if (!attachment) { + ret = -EINVAL; + goto done; + } + + ret = adf_device_detach_op(dev, eng, intf); + if (ret < 0) + goto done; + + adf_attachment_free(attachment); + dev->n_attached--; +done: + mutex_unlock(&dev->client_lock); + return ret; +} +EXPORT_SYMBOL(adf_device_detach); + +/** + * adf_interface_simple_buffer_alloc - allocate a simple buffer + * + * @intf: target interface + * @w: width in pixels + * @h: height in pixels + * @format: format fourcc + * @dma_buf: returns the allocated buffer + * @offset: returns the byte offset of the allocated buffer's first pixel + * @pitch: returns the allocated buffer's pitch + * + * See &struct adf_simple_buffer_alloc for a description of simple buffers and + * their limitations. + * + * Returns 0 on success or -errno on failure. + */ +int adf_interface_simple_buffer_alloc(struct adf_interface *intf, u16 w, u16 h, + u32 format, struct dma_buf **dma_buf, u32 *offset, u32 *pitch) +{ + if (!intf->ops || !intf->ops->alloc_simple_buffer) + return -EOPNOTSUPP; + + if (!adf_format_is_rgb(format)) + return -EINVAL; + + return intf->ops->alloc_simple_buffer(intf, w, h, format, dma_buf, + offset, pitch); +} +EXPORT_SYMBOL(adf_interface_simple_buffer_alloc); + +/** + * adf_interface_simple_post - flip to a single buffer + * + * @intf: interface targeted by the flip + * @buf: buffer to display + * + * adf_interface_simple_post() can be used generically for simple display + * configurations, since the client does not need to provide any driver-private + * configuration data. + * + * adf_interface_simple_post() has the same copying semantics as + * adf_device_post(). + * + * On success, returns a sync fence which signals when the buffer is removed + * from the screen. On failure, returns ERR_PTR(-errno). + */ +struct sync_fence *adf_interface_simple_post(struct adf_interface *intf, + struct adf_buffer *buf) +{ + size_t custom_data_size = 0; + void *custom_data = NULL; + struct sync_fence *ret; + + if (intf->ops && intf->ops->describe_simple_post) { + int err; + + custom_data = kzalloc(ADF_MAX_CUSTOM_DATA_SIZE, GFP_KERNEL); + if (!custom_data) { + ret = ERR_PTR(-ENOMEM); + goto done; + } + + err = intf->ops->describe_simple_post(intf, buf, custom_data, + &custom_data_size); + if (err < 0) { + ret = ERR_PTR(err); + goto done; + } + } + + ret = adf_device_post(adf_interface_parent(intf), &intf, 1, buf, 1, + custom_data, custom_data_size); +done: + kfree(custom_data); + return ret; +} +EXPORT_SYMBOL(adf_interface_simple_post); diff --git a/drivers/video/adf/adf_fbdev.c b/drivers/video/adf/adf_fbdev.c new file mode 100644 index 00000000000..477abd63ccc --- /dev/null +++ b/drivers/video/adf/adf_fbdev.c @@ -0,0 +1,651 @@ +/* + * Copyright (C) 2013 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/vmalloc.h> + +#include <video/adf.h> +#include <video/adf_client.h> +#include <video/adf_fbdev.h> +#include <video/adf_format.h> + +#include "adf.h" + +struct adf_fbdev_format { + u32 fourcc; + u32 bpp; + u32 r_length; + u32 g_length; + u32 b_length; + u32 a_length; + u32 r_offset; + u32 g_offset; + u32 b_offset; + u32 a_offset; +}; + +static const struct adf_fbdev_format format_table[] = { + {DRM_FORMAT_RGB332, 8, 3, 3, 2, 0, 5, 2, 0, 0}, + {DRM_FORMAT_BGR233, 8, 3, 3, 2, 0, 0, 3, 5, 0}, + + {DRM_FORMAT_XRGB4444, 16, 4, 4, 4, 0, 8, 4, 0, 0}, + {DRM_FORMAT_XBGR4444, 16, 4, 4, 4, 0, 0, 4, 8, 0}, + {DRM_FORMAT_RGBX4444, 16, 4, 4, 4, 0, 12, 8, 4, 0}, + {DRM_FORMAT_BGRX4444, 16, 4, 4, 4, 0, 0, 4, 8, 0}, + + {DRM_FORMAT_ARGB4444, 16, 4, 4, 4, 4, 8, 4, 0, 12}, + {DRM_FORMAT_ABGR4444, 16, 4, 4, 4, 4, 0, 4, 8, 12}, + {DRM_FORMAT_RGBA4444, 16, 4, 4, 4, 4, 12, 8, 4, 0}, + {DRM_FORMAT_BGRA4444, 16, 4, 4, 4, 4, 0, 4, 8, 0}, + + {DRM_FORMAT_XRGB1555, 16, 5, 5, 5, 0, 10, 5, 0, 0}, + {DRM_FORMAT_XBGR1555, 16, 5, 5, 5, 0, 0, 5, 10, 0}, + {DRM_FORMAT_RGBX5551, 16, 5, 5, 5, 0, 11, 6, 1, 0}, + {DRM_FORMAT_BGRX5551, 16, 5, 5, 5, 0, 1, 6, 11, 0}, + + {DRM_FORMAT_ARGB1555, 16, 5, 5, 5, 1, 10, 5, 0, 15}, + {DRM_FORMAT_ABGR1555, 16, 5, 5, 5, 1, 0, 5, 10, 15}, + {DRM_FORMAT_RGBA5551, 16, 5, 5, 5, 1, 11, 6, 1, 0}, + {DRM_FORMAT_BGRA5551, 16, 5, 5, 5, 1, 1, 6, 11, 0}, + + {DRM_FORMAT_RGB565, 16, 5, 6, 5, 0, 11, 5, 0, 0}, + {DRM_FORMAT_BGR565, 16, 5, 6, 5, 0, 0, 5, 11, 0}, + + {DRM_FORMAT_RGB888, 24, 8, 8, 8, 0, 16, 8, 0, 0}, + {DRM_FORMAT_BGR888, 24, 8, 8, 8, 0, 0, 8, 16, 0}, + + {DRM_FORMAT_XRGB8888, 32, 8, 8, 8, 0, 16, 8, 0, 0}, + {DRM_FORMAT_XBGR8888, 32, 8, 8, 8, 0, 0, 8, 16, 0}, + {DRM_FORMAT_RGBX8888, 32, 8, 8, 8, 0, 24, 16, 8, 0}, + {DRM_FORMAT_BGRX8888, 32, 8, 8, 8, 0, 8, 16, 24, 0}, + + {DRM_FORMAT_ARGB8888, 32, 8, 8, 8, 8, 16, 8, 0, 24}, + {DRM_FORMAT_ABGR8888, 32, 8, 8, 8, 8, 0, 8, 16, 24}, + {DRM_FORMAT_RGBA8888, 32, 8, 8, 8, 8, 24, 16, 8, 0}, + {DRM_FORMAT_BGRA8888, 32, 8, 8, 8, 8, 8, 16, 24, 0}, + + {DRM_FORMAT_XRGB2101010, 32, 10, 10, 10, 0, 20, 10, 0, 0}, + {DRM_FORMAT_XBGR2101010, 32, 10, 10, 10, 0, 0, 10, 20, 0}, + {DRM_FORMAT_RGBX1010102, 32, 10, 10, 10, 0, 22, 12, 2, 0}, + {DRM_FORMAT_BGRX1010102, 32, 10, 10, 10, 0, 2, 12, 22, 0}, + + {DRM_FORMAT_ARGB2101010, 32, 10, 10, 10, 2, 20, 10, 0, 30}, + {DRM_FORMAT_ABGR2101010, 32, 10, 10, 10, 2, 0, 10, 20, 30}, + {DRM_FORMAT_RGBA1010102, 32, 10, 10, 10, 2, 22, 12, 2, 0}, + {DRM_FORMAT_BGRA1010102, 32, 10, 10, 10, 2, 2, 12, 22, 0}, +}; + +static u32 drm_fourcc_from_fb_var(struct fb_var_screeninfo *var) +{ + size_t i; + for (i = 0; i < ARRAY_SIZE(format_table); i++) { + const struct adf_fbdev_format *f = &format_table[i]; + if (var->red.length == f->r_length && + var->red.offset == f->r_offset && + var->green.length == f->g_length && + var->green.offset == f->g_offset && + var->blue.length == f->b_length && + var->blue.offset == f->b_offset && + var->transp.length == f->a_length && + (var->transp.length == 0 || + var->transp.offset == f->a_offset)) + return f->fourcc; + } + + return 0; +} + +static const struct adf_fbdev_format *fbdev_format_info(u32 format) +{ + size_t i; + for (i = 0; i < ARRAY_SIZE(format_table); i++) { + const struct adf_fbdev_format *f = &format_table[i]; + if (f->fourcc == format) + return f; + } + + BUG(); +} + +void adf_modeinfo_to_fb_videomode(const struct drm_mode_modeinfo *mode, + struct fb_videomode *vmode) +{ + memset(vmode, 0, sizeof(*vmode)); + + vmode->refresh = mode->vrefresh; + + vmode->xres = mode->hdisplay; + vmode->yres = mode->vdisplay; + + vmode->pixclock = mode->clock ? KHZ2PICOS(mode->clock) : 0; + vmode->left_margin = mode->htotal - mode->hsync_end; + vmode->right_margin = mode->hsync_start - mode->hdisplay; + vmode->upper_margin = mode->vtotal - mode->vsync_end; + vmode->lower_margin = mode->vsync_start - mode->vdisplay; + vmode->hsync_len = mode->hsync_end - mode->hsync_start; + vmode->vsync_len = mode->vsync_end - mode->vsync_start; + + vmode->sync = 0; + if (mode->flags | DRM_MODE_FLAG_PHSYNC) + vmode->sync |= FB_SYNC_HOR_HIGH_ACT; + if (mode->flags | DRM_MODE_FLAG_PVSYNC) + vmode->sync |= FB_SYNC_VERT_HIGH_ACT; + if (mode->flags | DRM_MODE_FLAG_PCSYNC) + vmode->sync |= FB_SYNC_COMP_HIGH_ACT; + if (mode->flags | DRM_MODE_FLAG_BCAST) + vmode->sync |= FB_SYNC_BROADCAST; + + vmode->vmode = 0; + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + vmode->vmode |= FB_VMODE_INTERLACED; + if (mode->flags & DRM_MODE_FLAG_DBLSCAN) + vmode->vmode |= FB_VMODE_DOUBLE; +} +EXPORT_SYMBOL(adf_modeinfo_to_fb_videomode); + +void adf_modeinfo_from_fb_videomode(const struct fb_videomode *vmode, + struct drm_mode_modeinfo *mode) +{ + memset(mode, 0, sizeof(*mode)); + + mode->hdisplay = vmode->xres; + mode->hsync_start = mode->hdisplay + vmode->right_margin; + mode->hsync_end = mode->hsync_start + vmode->hsync_len; + mode->htotal = mode->hsync_end + vmode->left_margin; + + mode->vdisplay = vmode->yres; + mode->vsync_start = mode->vdisplay + vmode->lower_margin; + mode->vsync_end = mode->vsync_start + vmode->vsync_len; + mode->vtotal = mode->vsync_end + vmode->upper_margin; + + mode->clock = vmode->pixclock ? PICOS2KHZ(vmode->pixclock) : 0; + + mode->flags = 0; + if (vmode->sync & FB_SYNC_HOR_HIGH_ACT) + mode->flags |= DRM_MODE_FLAG_PHSYNC; + if (vmode->sync & FB_SYNC_VERT_HIGH_ACT) + mode->flags |= DRM_MODE_FLAG_PVSYNC; + if (vmode->sync & FB_SYNC_COMP_HIGH_ACT) + mode->flags |= DRM_MODE_FLAG_PCSYNC; + if (vmode->sync & FB_SYNC_BROADCAST) + mode->flags |= DRM_MODE_FLAG_BCAST; + if (vmode->vmode & FB_VMODE_INTERLACED) + mode->flags |= DRM_MODE_FLAG_INTERLACE; + if (vmode->vmode & FB_VMODE_DOUBLE) + mode->flags |= DRM_MODE_FLAG_DBLSCAN; + + if (vmode->refresh) + mode->vrefresh = vmode->refresh; + else + adf_modeinfo_set_vrefresh(mode); + + if (vmode->name) + strlcpy(mode->name, vmode->name, sizeof(mode->name)); + else + adf_modeinfo_set_name(mode); +} +EXPORT_SYMBOL(adf_modeinfo_from_fb_videomode); + +static int adf_fbdev_post(struct adf_fbdev *fbdev) +{ + struct adf_buffer buf; + struct sync_fence *complete_fence; + int ret = 0; + + memset(&buf, 0, sizeof(buf)); + buf.overlay_engine = fbdev->eng; + buf.w = fbdev->info->var.xres; + buf.h = fbdev->info->var.yres; + buf.format = fbdev->format; + buf.dma_bufs[0] = fbdev->dma_buf; + buf.offset[0] = fbdev->offset + + fbdev->info->var.yoffset * fbdev->pitch + + fbdev->info->var.xoffset * + (fbdev->info->var.bits_per_pixel / 8); + buf.pitch[0] = fbdev->pitch; + buf.n_planes = 1; + + complete_fence = adf_interface_simple_post(fbdev->intf, &buf); + if (IS_ERR(complete_fence)) { + ret = PTR_ERR(complete_fence); + goto done; + } + + sync_fence_put(complete_fence); +done: + return ret; +} + +static const u16 vga_palette[][3] = { + {0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0xAAAA}, + {0x0000, 0xAAAA, 0x0000}, + {0x0000, 0xAAAA, 0xAAAA}, + {0xAAAA, 0x0000, 0x0000}, + {0xAAAA, 0x0000, 0xAAAA}, + {0xAAAA, 0x5555, 0x0000}, + {0xAAAA, 0xAAAA, 0xAAAA}, + {0x5555, 0x5555, 0x5555}, + {0x5555, 0x5555, 0xFFFF}, + {0x5555, 0xFFFF, 0x5555}, + {0x5555, 0xFFFF, 0xFFFF}, + {0xFFFF, 0x5555, 0x5555}, + {0xFFFF, 0x5555, 0xFFFF}, + {0xFFFF, 0xFFFF, 0x5555}, + {0xFFFF, 0xFFFF, 0xFFFF}, +}; + +static int adf_fb_alloc(struct adf_fbdev *fbdev) +{ + int ret; + + ret = adf_interface_simple_buffer_alloc(fbdev->intf, + fbdev->default_xres_virtual, + fbdev->default_yres_virtual, + fbdev->default_format, + &fbdev->dma_buf, &fbdev->offset, &fbdev->pitch); + if (ret < 0) { + dev_err(fbdev->info->dev, "allocating fb failed: %d\n", ret); + return ret; + } + + fbdev->vaddr = dma_buf_vmap(fbdev->dma_buf); + if (!fbdev->vaddr) { + ret = -ENOMEM; + dev_err(fbdev->info->dev, "vmapping fb failed\n"); + goto err_vmap; + } + fbdev->info->fix.line_length = fbdev->pitch; + fbdev->info->var.xres_virtual = fbdev->default_xres_virtual; + fbdev->info->var.yres_virtual = fbdev->default_yres_virtual; + fbdev->info->fix.smem_len = fbdev->dma_buf->size; + fbdev->info->screen_base = fbdev->vaddr; + + return 0; + +err_vmap: + dma_buf_put(fbdev->dma_buf); + return ret; +} + +static void adf_fb_destroy(struct adf_fbdev *fbdev) +{ + dma_buf_vunmap(fbdev->dma_buf, fbdev->vaddr); + dma_buf_put(fbdev->dma_buf); +} + +static void adf_fbdev_set_format(struct adf_fbdev *fbdev, u32 format) +{ + size_t i; + const struct adf_fbdev_format *info = fbdev_format_info(format); + for (i = 0; i < ARRAY_SIZE(vga_palette); i++) { + u16 r = vga_palette[i][0]; + u16 g = vga_palette[i][1]; + u16 b = vga_palette[i][2]; + + r >>= (16 - info->r_length); + g >>= (16 - info->g_length); + b >>= (16 - info->b_length); + + fbdev->pseudo_palette[i] = + (r << info->r_offset) | + (g << info->g_offset) | + (b << info->b_offset); + + if (info->a_length) { + u16 a = BIT(info->a_length) - 1; + fbdev->pseudo_palette[i] |= (a << info->a_offset); + } + } + + fbdev->info->var.bits_per_pixel = adf_format_bpp(format); + fbdev->info->var.red.length = info->r_length; + fbdev->info->var.red.offset = info->r_offset; + fbdev->info->var.green.length = info->g_length; + fbdev->info->var.green.offset = info->g_offset; + fbdev->info->var.blue.length = info->b_length; + fbdev->info->var.blue.offset = info->b_offset; + fbdev->info->var.transp.length = info->a_length; + fbdev->info->var.transp.offset = info->a_offset; + fbdev->format = format; +} + +static void adf_fbdev_fill_modelist(struct adf_fbdev *fbdev) +{ + struct drm_mode_modeinfo *modelist; + struct fb_videomode fbmode; + size_t n_modes, i; + int ret = 0; + + n_modes = adf_interface_modelist(fbdev->intf, NULL, 0); + modelist = kzalloc(sizeof(modelist[0]) * n_modes, GFP_KERNEL); + if (!modelist) { + dev_warn(fbdev->info->dev, "allocating new modelist failed; keeping old modelist\n"); + return; + } + adf_interface_modelist(fbdev->intf, modelist, n_modes); + + fb_destroy_modelist(&fbdev->info->modelist); + + for (i = 0; i < n_modes; i++) { + adf_modeinfo_to_fb_videomode(&modelist[i], &fbmode); + ret = fb_add_videomode(&fbmode, &fbdev->info->modelist); + if (ret < 0) + dev_warn(fbdev->info->dev, "adding mode %s to modelist failed: %d\n", + modelist[i].name, ret); + } + + kfree(modelist); +} + +/** + * adf_fbdev_open - default implementation of fbdev open op + */ +int adf_fbdev_open(struct fb_info *info, int user) +{ + struct adf_fbdev *fbdev = info->par; + int ret; + + if (!fbdev->open) { + struct drm_mode_modeinfo mode; + struct fb_videomode fbmode; + struct adf_device *dev = adf_interface_parent(fbdev->intf); + + ret = adf_device_attach(dev, fbdev->eng, fbdev->intf); + if (ret < 0 && ret != -EALREADY) + return ret; + + ret = adf_fb_alloc(fbdev); + if (ret < 0) + return ret; + + adf_interface_current_mode(fbdev->intf, &mode); + adf_modeinfo_to_fb_videomode(&mode, &fbmode); + fb_videomode_to_var(&fbdev->info->var, &fbmode); + + adf_fbdev_set_format(fbdev, fbdev->default_format); + adf_fbdev_fill_modelist(fbdev); + } + + ret = adf_fbdev_post(fbdev); + if (ret < 0) { + if (!fbdev->open) + adf_fb_destroy(fbdev); + return ret; + } + + fbdev->open = true; + return 0; +} +EXPORT_SYMBOL(adf_fbdev_open); + +/** + * adf_fbdev_release - default implementation of fbdev release op + */ +int adf_fbdev_release(struct fb_info *info, int user) +{ + struct adf_fbdev *fbdev = info->par; + adf_fb_destroy(fbdev); + fbdev->open = false; + return 0; +} +EXPORT_SYMBOL(adf_fbdev_release); + +/** + * adf_fbdev_check_var - default implementation of fbdev check_var op + */ +int adf_fbdev_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct adf_fbdev *fbdev = info->par; + bool valid_format = true; + u32 format = drm_fourcc_from_fb_var(var); + u32 pitch = var->xres_virtual * var->bits_per_pixel / 8; + + if (!format) { + dev_dbg(info->dev, "%s: unrecognized format\n", __func__); + valid_format = false; + } + + if (valid_format && var->grayscale) { + dev_dbg(info->dev, "%s: grayscale modes not supported\n", + __func__); + valid_format = false; + } + + if (valid_format && var->nonstd) { + dev_dbg(info->dev, "%s: nonstandard formats not supported\n", + __func__); + valid_format = false; + } + + if (valid_format && !adf_overlay_engine_supports_format(fbdev->eng, + format)) { + char format_str[ADF_FORMAT_STR_SIZE]; + adf_format_str(format, format_str); + dev_dbg(info->dev, "%s: format %s not supported by overlay engine %s\n", + __func__, format_str, fbdev->eng->base.name); + valid_format = false; + } + + if (valid_format && pitch > fbdev->pitch) { + dev_dbg(info->dev, "%s: fb pitch too small for var (pitch = %u, xres_virtual = %u, bits_per_pixel = %u)\n", + __func__, fbdev->pitch, var->xres_virtual, + var->bits_per_pixel); + valid_format = false; + } + + if (valid_format && var->yres_virtual > fbdev->default_yres_virtual) { + dev_dbg(info->dev, "%s: fb height too small for var (h = %u, yres_virtual = %u)\n", + __func__, fbdev->default_yres_virtual, + var->yres_virtual); + valid_format = false; + } + + if (valid_format) { + var->activate = info->var.activate; + var->height = info->var.height; + var->width = info->var.width; + var->accel_flags = info->var.accel_flags; + var->rotate = info->var.rotate; + var->colorspace = info->var.colorspace; + /* userspace can't change these */ + } else { + /* if any part of the format is invalid then fixing it up is + impractical, so save just the modesetting bits and + overwrite everything else */ + struct fb_videomode mode; + fb_var_to_videomode(&mode, var); + memcpy(var, &info->var, sizeof(*var)); + fb_videomode_to_var(var, &mode); + } + + return 0; +} +EXPORT_SYMBOL(adf_fbdev_check_var); + +/** + * adf_fbdev_set_par - default implementation of fbdev set_par op + */ +int adf_fbdev_set_par(struct fb_info *info) +{ + struct adf_fbdev *fbdev = info->par; + struct adf_interface *intf = fbdev->intf; + struct fb_videomode vmode; + struct drm_mode_modeinfo mode; + int ret; + u32 format = drm_fourcc_from_fb_var(&info->var); + + fb_var_to_videomode(&vmode, &info->var); + adf_modeinfo_from_fb_videomode(&vmode, &mode); + ret = adf_interface_set_mode(intf, &mode); + if (ret < 0) + return ret; + + ret = adf_fbdev_post(fbdev); + if (ret < 0) + return ret; + + if (format != fbdev->format) + adf_fbdev_set_format(fbdev, format); + + return 0; +} +EXPORT_SYMBOL(adf_fbdev_set_par); + +/** + * adf_fbdev_blank - default implementation of fbdev blank op + */ +int adf_fbdev_blank(int blank, struct fb_info *info) +{ + struct adf_fbdev *fbdev = info->par; + struct adf_interface *intf = fbdev->intf; + u8 dpms_state; + + switch (blank) { + case FB_BLANK_UNBLANK: + dpms_state = DRM_MODE_DPMS_ON; + break; + case FB_BLANK_NORMAL: + dpms_state = DRM_MODE_DPMS_STANDBY; + break; + case FB_BLANK_VSYNC_SUSPEND: + dpms_state = DRM_MODE_DPMS_STANDBY; + break; + case FB_BLANK_HSYNC_SUSPEND: + dpms_state = DRM_MODE_DPMS_SUSPEND; + break; + case FB_BLANK_POWERDOWN: + dpms_state = DRM_MODE_DPMS_OFF; + break; + default: + return -EINVAL; + } + + return adf_interface_blank(intf, dpms_state); +} +EXPORT_SYMBOL(adf_fbdev_blank); + +/** + * adf_fbdev_pan_display - default implementation of fbdev pan_display op + */ +int adf_fbdev_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct adf_fbdev *fbdev = info->par; + return adf_fbdev_post(fbdev); +} +EXPORT_SYMBOL(adf_fbdev_pan_display); + +/** + * adf_fbdev_mmap - default implementation of fbdev mmap op + */ +int adf_fbdev_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct adf_fbdev *fbdev = info->par; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + return dma_buf_mmap(fbdev->dma_buf, vma, 0); +} +EXPORT_SYMBOL(adf_fbdev_mmap); + +/** + * adf_fbdev_init - initialize helper to wrap ADF device in fbdev API + * + * @fbdev: the fbdev helper + * @interface: the ADF interface that will display the framebuffer + * @eng: the ADF overlay engine that will scan out the framebuffer + * @xres_virtual: the virtual width of the framebuffer + * @yres_virtual: the virtual height of the framebuffer + * @format: the format of the framebuffer + * @fbops: the device's fbdev ops + * @fmt: formatting for the framebuffer identification string + * @...: variable arguments + * + * @format must be a standard, non-indexed RGB format, i.e., + * adf_format_is_rgb(@format) && @format != @DRM_FORMAT_C8. + * + * Returns 0 on success or -errno on failure. + */ +int adf_fbdev_init(struct adf_fbdev *fbdev, struct adf_interface *interface, + struct adf_overlay_engine *eng, + u16 xres_virtual, u16 yres_virtual, u32 format, + struct fb_ops *fbops, const char *fmt, ...) +{ + struct adf_device *parent = adf_interface_parent(interface); + struct device *dev = &parent->base.dev; + u16 width_mm, height_mm; + va_list args; + int ret; + + if (!adf_format_is_rgb(format) || + format == DRM_FORMAT_C8) { + dev_err(dev, "fbdev helper does not support format %u\n", + format); + return -EINVAL; + } + + memset(fbdev, 0, sizeof(*fbdev)); + fbdev->intf = interface; + fbdev->eng = eng; + fbdev->info = framebuffer_alloc(0, dev); + if (!fbdev->info) { + dev_err(dev, "allocating framebuffer device failed\n"); + return -ENOMEM; + } + fbdev->default_xres_virtual = xres_virtual; + fbdev->default_yres_virtual = yres_virtual; + fbdev->default_format = format; + + fbdev->info->flags = FBINFO_FLAG_DEFAULT; + ret = adf_interface_get_screen_size(interface, &width_mm, &height_mm); + if (ret < 0) { + width_mm = 0; + height_mm = 0; + } + fbdev->info->var.width = width_mm; + fbdev->info->var.height = height_mm; + fbdev->info->var.activate = FB_ACTIVATE_VBL; + va_start(args, fmt); + vsnprintf(fbdev->info->fix.id, sizeof(fbdev->info->fix.id), fmt, args); + va_end(args); + fbdev->info->fix.type = FB_TYPE_PACKED_PIXELS; + fbdev->info->fix.visual = FB_VISUAL_TRUECOLOR; + fbdev->info->fix.xpanstep = 1; + fbdev->info->fix.ypanstep = 1; + INIT_LIST_HEAD(&fbdev->info->modelist); + fbdev->info->fbops = fbops; + fbdev->info->pseudo_palette = fbdev->pseudo_palette; + fbdev->info->par = fbdev; + + ret = register_framebuffer(fbdev->info); + if (ret < 0) { + dev_err(dev, "registering framebuffer failed: %d\n", ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL(adf_fbdev_init); + +/** + * adf_fbdev_destroy - destroy helper to wrap ADF device in fbdev API + * + * @fbdev: the fbdev helper + */ +void adf_fbdev_destroy(struct adf_fbdev *fbdev) +{ + unregister_framebuffer(fbdev->info); + if (WARN_ON(fbdev->open)) + adf_fb_destroy(fbdev); + framebuffer_release(fbdev->info); +} +EXPORT_SYMBOL(adf_fbdev_destroy); diff --git a/drivers/video/adf/adf_fops.c b/drivers/video/adf/adf_fops.c new file mode 100644 index 00000000000..abec58ea2ed --- /dev/null +++ b/drivers/video/adf/adf_fops.c @@ -0,0 +1,957 @@ +/* + * Copyright (C) 2013 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/bitops.h> +#include <linux/circ_buf.h> +#include <linux/fs.h> +#include <linux/module.h> +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#include <video/adf_client.h> +#include <video/adf_format.h> + +#include "sw_sync.h" +#include "sync.h" + +#include "adf.h" +#include "adf_fops.h" +#include "adf_sysfs.h" + +#ifdef CONFIG_COMPAT +#include "adf_fops32.h" +#endif + +static int adf_obj_set_event(struct adf_obj *obj, struct adf_file *file, + struct adf_set_event __user *arg) +{ + struct adf_set_event data; + bool enabled; + unsigned long flags; + int err; + + if (copy_from_user(&data, arg, sizeof(data))) + return -EFAULT; + + err = adf_obj_check_supports_event(obj, data.type); + if (err < 0) + return err; + + spin_lock_irqsave(&obj->file_lock, flags); + if (data.enabled) + enabled = test_and_set_bit(data.type, + file->event_subscriptions); + else + enabled = test_and_clear_bit(data.type, + file->event_subscriptions); + spin_unlock_irqrestore(&obj->file_lock, flags); + + if (data.enabled == enabled) + return -EALREADY; + + if (data.enabled) + adf_event_get(obj, data.type); + else + adf_event_put(obj, data.type); + + return 0; +} + +static int adf_obj_copy_custom_data_to_user(struct adf_obj *obj, + void __user *dst, size_t *dst_size) +{ + void *custom_data; + size_t custom_data_size; + int ret; + + if (!obj->ops || !obj->ops->custom_data) { + dev_dbg(&obj->dev, "%s: no custom_data op\n", __func__); + return 0; + } + + custom_data = kzalloc(ADF_MAX_CUSTOM_DATA_SIZE, GFP_KERNEL); + if (!custom_data) + return -ENOMEM; + + ret = obj->ops->custom_data(obj, custom_data, &custom_data_size); + if (ret < 0) + goto done; + + if (copy_to_user(dst, custom_data, min(*dst_size, custom_data_size))) { + ret = -EFAULT; + goto done; + } + *dst_size = custom_data_size; + +done: + kfree(custom_data); + return ret; +} + +static int adf_eng_get_data(struct adf_overlay_engine *eng, + struct adf_overlay_engine_data __user *arg) +{ + struct adf_device *dev = adf_overlay_engine_parent(eng); + struct adf_overlay_engine_data data; + size_t n_supported_formats; + u32 *supported_formats = NULL; + int ret = 0; + + if (copy_from_user(&data, arg, sizeof(data))) + return -EFAULT; + + strlcpy(data.name, eng->base.name, sizeof(data.name)); + + if (data.n_supported_formats > ADF_MAX_SUPPORTED_FORMATS) + return -EINVAL; + + n_supported_formats = data.n_supported_formats; + data.n_supported_formats = eng->ops->n_supported_formats; + + if (n_supported_formats) { + supported_formats = kzalloc(n_supported_formats * + sizeof(supported_formats[0]), GFP_KERNEL); + if (!supported_formats) + return -ENOMEM; + } + + memcpy(supported_formats, eng->ops->supported_formats, + sizeof(u32) * min(n_supported_formats, + eng->ops->n_supported_formats)); + + mutex_lock(&dev->client_lock); + ret = adf_obj_copy_custom_data_to_user(&eng->base, arg->custom_data, + &data.custom_data_size); + mutex_unlock(&dev->client_lock); + + if (ret < 0) + goto done; + + if (copy_to_user(arg, &data, sizeof(data))) { + ret = -EFAULT; + goto done; + } + + if (supported_formats && copy_to_user(arg->supported_formats, + supported_formats, + n_supported_formats * sizeof(supported_formats[0]))) + ret = -EFAULT; + +done: + kfree(supported_formats); + return ret; +} + +static int adf_buffer_import(struct adf_device *dev, + struct adf_buffer_config __user *cfg, struct adf_buffer *buf) +{ + struct adf_buffer_config user_buf; + size_t i; + int ret = 0; + + if (copy_from_user(&user_buf, cfg, sizeof(user_buf))) + return -EFAULT; + + memset(buf, 0, sizeof(*buf)); + + if (user_buf.n_planes > ADF_MAX_PLANES) { + dev_err(&dev->base.dev, "invalid plane count %u\n", + user_buf.n_planes); + return -EINVAL; + } + + buf->overlay_engine = idr_find(&dev->overlay_engines, + user_buf.overlay_engine); + if (!buf->overlay_engine) { + dev_err(&dev->base.dev, "invalid overlay engine id %u\n", + user_buf.overlay_engine); + return -ENOENT; + } + + buf->w = user_buf.w; + buf->h = user_buf.h; + buf->format = user_buf.format; + for (i = 0; i < user_buf.n_planes; i++) { + buf->dma_bufs[i] = dma_buf_get(user_buf.fd[i]); + if (IS_ERR(buf->dma_bufs[i])) { + ret = PTR_ERR(buf->dma_bufs[i]); + dev_err(&dev->base.dev, "importing dma_buf fd %llu failed: %d\n", + user_buf.fd[i], ret); + buf->dma_bufs[i] = NULL; + goto done; + } + buf->offset[i] = user_buf.offset[i]; + buf->pitch[i] = user_buf.pitch[i]; + } + buf->n_planes = user_buf.n_planes; + + if (user_buf.acquire_fence >= 0) { + buf->acquire_fence = sync_fence_fdget(user_buf.acquire_fence); + if (!buf->acquire_fence) { + dev_err(&dev->base.dev, "getting fence fd %lld failed\n", + user_buf.acquire_fence); + ret = -EINVAL; + goto done; + } + } + +done: + if (ret < 0) + adf_buffer_cleanup(buf); + return ret; +} + +static int adf_device_post_config(struct adf_device *dev, + struct adf_post_config __user *arg) +{ + struct sync_fence *complete_fence; + int complete_fence_fd; + struct adf_buffer *bufs = NULL; + struct adf_interface **intfs = NULL; + size_t n_intfs, n_bufs, i; + void *custom_data = NULL; + size_t custom_data_size; + int ret = 0; + + complete_fence_fd = get_unused_fd(); + if (complete_fence_fd < 0) + return complete_fence_fd; + + if (get_user(n_intfs, &arg->n_interfaces)) { + ret = -EFAULT; + goto err_get_user; + } + + if (n_intfs > ADF_MAX_INTERFACES) { + ret = -EINVAL; + goto err_get_user; + } + + if (get_user(n_bufs, &arg->n_bufs)) { + ret = -EFAULT; + goto err_get_user; + } + + if (n_bufs > ADF_MAX_BUFFERS) { + ret = -EINVAL; + goto err_get_user; + } + + if (get_user(custom_data_size, &arg->custom_data_size)) { + ret = -EFAULT; + goto err_get_user; + } + + if (custom_data_size > ADF_MAX_CUSTOM_DATA_SIZE) { + ret = -EINVAL; + goto err_get_user; + } + + if (n_intfs) { + intfs = kmalloc(sizeof(intfs[0]) * n_intfs, GFP_KERNEL); + if (!intfs) { + ret = -ENOMEM; + goto err_get_user; + } + } + + for (i = 0; i < n_intfs; i++) { + u32 intf_id; + if (get_user(intf_id, &arg->interfaces[i])) { + ret = -EFAULT; + goto err_get_user; + } + + intfs[i] = idr_find(&dev->interfaces, intf_id); + if (!intfs[i]) { + ret = -EINVAL; + goto err_get_user; + } + } + + if (n_bufs) { + bufs = kzalloc(sizeof(bufs[0]) * n_bufs, GFP_KERNEL); + if (!bufs) { + ret = -ENOMEM; + goto err_get_user; + } + } + + for (i = 0; i < n_bufs; i++) { + ret = adf_buffer_import(dev, &arg->bufs[i], &bufs[i]); + if (ret < 0) { + memset(&bufs[i], 0, sizeof(bufs[i])); + goto err_import; + } + } + + if (custom_data_size) { + custom_data = kzalloc(custom_data_size, GFP_KERNEL); + if (!custom_data) { + ret = -ENOMEM; + goto err_import; + } + + if (copy_from_user(custom_data, arg->custom_data, + custom_data_size)) { + ret = -EFAULT; + goto err_import; + } + } + + if (put_user(complete_fence_fd, &arg->complete_fence)) { + ret = -EFAULT; + goto err_import; + } + + complete_fence = adf_device_post_nocopy(dev, intfs, n_intfs, bufs, + n_bufs, custom_data, custom_data_size); + if (IS_ERR(complete_fence)) { + ret = PTR_ERR(complete_fence); + goto err_import; + } + + sync_fence_install(complete_fence, complete_fence_fd); + return 0; + +err_import: + for (i = 0; i < n_bufs; i++) + adf_buffer_cleanup(&bufs[i]); + +err_get_user: + kfree(custom_data); + kfree(bufs); + kfree(intfs); + put_unused_fd(complete_fence_fd); + return ret; +} + +static int adf_intf_simple_post_config(struct adf_interface *intf, + struct adf_simple_post_config __user *arg) +{ + struct adf_device *dev = intf->base.parent; + struct sync_fence *complete_fence; + int complete_fence_fd; + struct adf_buffer buf; + int ret = 0; + + complete_fence_fd = get_unused_fd(); + if (complete_fence_fd < 0) + return complete_fence_fd; + + ret = adf_buffer_import(dev, &arg->buf, &buf); + if (ret < 0) + goto err_import; + + if (put_user(complete_fence_fd, &arg->complete_fence)) { + ret = -EFAULT; + goto err_put_user; + } + + complete_fence = adf_interface_simple_post(intf, &buf); + if (IS_ERR(complete_fence)) { + ret = PTR_ERR(complete_fence); + goto err_put_user; + } + + sync_fence_install(complete_fence, complete_fence_fd); + return 0; + +err_put_user: + adf_buffer_cleanup(&buf); +err_import: + put_unused_fd(complete_fence_fd); + return ret; +} + +static int adf_intf_simple_buffer_alloc(struct adf_interface *intf, + struct adf_simple_buffer_alloc __user *arg) +{ + struct adf_simple_buffer_alloc data; + struct dma_buf *dma_buf; + int ret = 0; + + if (copy_from_user(&data, arg, sizeof(data))) + return -EFAULT; + + data.fd = get_unused_fd_flags(O_CLOEXEC); + if (data.fd < 0) + return data.fd; + + ret = adf_interface_simple_buffer_alloc(intf, data.w, data.h, + data.format, &dma_buf, &data.offset, &data.pitch); + if (ret < 0) + goto err_alloc; + + if (copy_to_user(arg, &data, sizeof(*arg))) { + ret = -EFAULT; + goto err_copy; + } + + fd_install(data.fd, dma_buf->file); + return 0; + +err_copy: + dma_buf_put(dma_buf); + +err_alloc: + put_unused_fd(data.fd); + return ret; +} + +static int adf_copy_attachment_list_to_user( + struct adf_attachment_config __user *to, size_t n_to, + struct adf_attachment *from, size_t n_from) +{ + struct adf_attachment_config *temp; + size_t n = min(n_to, n_from); + size_t i; + int ret = 0; + + if (!n) + return 0; + + temp = kzalloc(n * sizeof(temp[0]), GFP_KERNEL); + if (!temp) + return -ENOMEM; + + for (i = 0; i < n; i++) { + temp[i].interface = from[i].interface->base.id; + temp[i].overlay_engine = from[i].overlay_engine->base.id; + } + + if (copy_to_user(to, temp, n * sizeof(to[0]))) { + ret = -EFAULT; + goto done; + } + +done: + kfree(temp); + return ret; +} + +static int adf_device_get_data(struct adf_device *dev, + struct adf_device_data __user *arg) +{ + struct adf_device_data data; + size_t n_attach; + struct adf_attachment *attach = NULL; + size_t n_allowed_attach; + struct adf_attachment *allowed_attach = NULL; + int ret = 0; + + if (copy_from_user(&data, arg, sizeof(data))) + return -EFAULT; + + if (data.n_attachments > ADF_MAX_ATTACHMENTS || + data.n_allowed_attachments > ADF_MAX_ATTACHMENTS) + return -EINVAL; + + strlcpy(data.name, dev->base.name, sizeof(data.name)); + + if (data.n_attachments) { + attach = kzalloc(data.n_attachments * sizeof(attach[0]), + GFP_KERNEL); + if (!attach) + return -ENOMEM; + } + n_attach = adf_device_attachments(dev, attach, data.n_attachments); + + if (data.n_allowed_attachments) { + allowed_attach = kzalloc(data.n_allowed_attachments * + sizeof(allowed_attach[0]), GFP_KERNEL); + if (!allowed_attach) { + ret = -ENOMEM; + goto done; + } + } + n_allowed_attach = adf_device_attachments_allowed(dev, allowed_attach, + data.n_allowed_attachments); + + mutex_lock(&dev->client_lock); + ret = adf_obj_copy_custom_data_to_user(&dev->base, arg->custom_data, + &data.custom_data_size); + mutex_unlock(&dev->client_lock); + + if (ret < 0) + goto done; + + ret = adf_copy_attachment_list_to_user(arg->attachments, + data.n_attachments, attach, n_attach); + if (ret < 0) + goto done; + + ret = adf_copy_attachment_list_to_user(arg->allowed_attachments, + data.n_allowed_attachments, allowed_attach, + n_allowed_attach); + if (ret < 0) + goto done; + + data.n_attachments = n_attach; + data.n_allowed_attachments = n_allowed_attach; + + if (copy_to_user(arg, &data, sizeof(data))) + ret = -EFAULT; + +done: + kfree(allowed_attach); + kfree(attach); + return ret; +} + +static int adf_device_handle_attachment(struct adf_device *dev, + struct adf_attachment_config __user *arg, bool attach) +{ + struct adf_attachment_config data; + struct adf_overlay_engine *eng; + struct adf_interface *intf; + + if (copy_from_user(&data, arg, sizeof(data))) + return -EFAULT; + + eng = idr_find(&dev->overlay_engines, data.overlay_engine); + if (!eng) { + dev_err(&dev->base.dev, "invalid overlay engine id %u\n", + data.overlay_engine); + return -EINVAL; + } + + intf = idr_find(&dev->interfaces, data.interface); + if (!intf) { + dev_err(&dev->base.dev, "invalid interface id %u\n", + data.interface); + return -EINVAL; + } + + if (attach) + return adf_device_attach(dev, eng, intf); + else + return adf_device_detach(dev, eng, intf); +} + +static int adf_intf_set_mode(struct adf_interface *intf, + struct drm_mode_modeinfo __user *arg) +{ + struct drm_mode_modeinfo mode; + + if (copy_from_user(&mode, arg, sizeof(mode))) + return -EFAULT; + + return adf_interface_set_mode(intf, &mode); +} + +static int adf_intf_get_data(struct adf_interface *intf, + struct adf_interface_data __user *arg) +{ + struct adf_device *dev = adf_interface_parent(intf); + struct adf_interface_data data; + struct drm_mode_modeinfo *modelist; + size_t modelist_size; + int err; + int ret = 0; + unsigned long flags; + + if (copy_from_user(&data, arg, sizeof(data))) + return -EFAULT; + + strlcpy(data.name, intf->base.name, sizeof(data.name)); + + data.type = intf->type; + data.id = intf->idx; + data.flags = intf->flags; + + err = adf_interface_get_screen_size(intf, &data.width_mm, + &data.height_mm); + if (err < 0) { + data.width_mm = 0; + data.height_mm = 0; + } + + modelist = kmalloc(sizeof(modelist[0]) * ADF_MAX_MODES, GFP_KERNEL); + if (!modelist) + return -ENOMEM; + + mutex_lock(&dev->client_lock); + read_lock_irqsave(&intf->hotplug_modelist_lock, flags); + data.hotplug_detect = intf->hotplug_detect; + modelist_size = min(data.n_available_modes, intf->n_modes) * + sizeof(intf->modelist[0]); + memcpy(modelist, intf->modelist, modelist_size); + data.n_available_modes = intf->n_modes; + read_unlock_irqrestore(&intf->hotplug_modelist_lock, flags); + + if (copy_to_user(arg->available_modes, modelist, modelist_size)) { + ret = -EFAULT; + goto done; + } + + data.dpms_state = intf->dpms_state; + memcpy(&data.current_mode, &intf->current_mode, + sizeof(intf->current_mode)); + + ret = adf_obj_copy_custom_data_to_user(&intf->base, arg->custom_data, + &data.custom_data_size); +done: + mutex_unlock(&dev->client_lock); + kfree(modelist); + + if (ret < 0) + return ret; + + if (copy_to_user(arg, &data, sizeof(data))) + ret = -EFAULT; + + return ret; +} + +static inline long adf_obj_custom_ioctl(struct adf_obj *obj, unsigned int cmd, + unsigned long arg) +{ + if (obj->ops && obj->ops->ioctl) + return obj->ops->ioctl(obj, cmd, arg); + return -ENOTTY; +} + +static long adf_overlay_engine_ioctl(struct adf_overlay_engine *eng, + struct adf_file *file, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case ADF_SET_EVENT: + return adf_obj_set_event(&eng->base, file, + (struct adf_set_event __user *)arg); + + case ADF_GET_OVERLAY_ENGINE_DATA: + return adf_eng_get_data(eng, + (struct adf_overlay_engine_data __user *)arg); + + case ADF_BLANK: + case ADF_POST_CONFIG: + case ADF_SET_MODE: + case ADF_GET_DEVICE_DATA: + case ADF_GET_INTERFACE_DATA: + case ADF_SIMPLE_POST_CONFIG: + case ADF_SIMPLE_BUFFER_ALLOC: + case ADF_ATTACH: + case ADF_DETACH: + return -EINVAL; + + default: + return adf_obj_custom_ioctl(&eng->base, cmd, arg); + } +} + +static long adf_interface_ioctl(struct adf_interface *intf, + struct adf_file *file, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case ADF_SET_EVENT: + return adf_obj_set_event(&intf->base, file, + (struct adf_set_event __user *)arg); + + case ADF_BLANK: + return adf_interface_blank(intf, arg); + + case ADF_SET_MODE: + return adf_intf_set_mode(intf, + (struct drm_mode_modeinfo __user *)arg); + + case ADF_GET_INTERFACE_DATA: + return adf_intf_get_data(intf, + (struct adf_interface_data __user *)arg); + + case ADF_SIMPLE_POST_CONFIG: + return adf_intf_simple_post_config(intf, + (struct adf_simple_post_config __user *)arg); + + case ADF_SIMPLE_BUFFER_ALLOC: + return adf_intf_simple_buffer_alloc(intf, + (struct adf_simple_buffer_alloc __user *)arg); + + case ADF_POST_CONFIG: + case ADF_GET_DEVICE_DATA: + case ADF_GET_OVERLAY_ENGINE_DATA: + case ADF_ATTACH: + case ADF_DETACH: + return -EINVAL; + + default: + return adf_obj_custom_ioctl(&intf->base, cmd, arg); + } +} + +static long adf_device_ioctl(struct adf_device *dev, struct adf_file *file, + unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case ADF_SET_EVENT: + return adf_obj_set_event(&dev->base, file, + (struct adf_set_event __user *)arg); + + case ADF_POST_CONFIG: + return adf_device_post_config(dev, + (struct adf_post_config __user *)arg); + + case ADF_GET_DEVICE_DATA: + return adf_device_get_data(dev, + (struct adf_device_data __user *)arg); + + case ADF_ATTACH: + return adf_device_handle_attachment(dev, + (struct adf_attachment_config __user *)arg, + true); + + case ADF_DETACH: + return adf_device_handle_attachment(dev, + (struct adf_attachment_config __user *)arg, + false); + + case ADF_BLANK: + case ADF_SET_MODE: + case ADF_GET_INTERFACE_DATA: + case ADF_GET_OVERLAY_ENGINE_DATA: + case ADF_SIMPLE_POST_CONFIG: + case ADF_SIMPLE_BUFFER_ALLOC: + return -EINVAL; + + default: + return adf_obj_custom_ioctl(&dev->base, cmd, arg); + } +} + +static int adf_file_open(struct inode *inode, struct file *file) +{ + struct adf_obj *obj; + struct adf_file *fpriv = NULL; + unsigned long flags; + int ret = 0; + + obj = adf_obj_sysfs_find(iminor(inode)); + if (!obj) + return -ENODEV; + + dev_dbg(&obj->dev, "opening %s\n", dev_name(&obj->dev)); + + if (!try_module_get(obj->parent->ops->owner)) { + dev_err(&obj->dev, "getting owner module failed\n"); + return -ENODEV; + } + + fpriv = kzalloc(sizeof(*fpriv), GFP_KERNEL); + if (!fpriv) { + ret = -ENOMEM; + goto done; + } + + INIT_LIST_HEAD(&fpriv->head); + fpriv->obj = obj; + init_waitqueue_head(&fpriv->event_wait); + + file->private_data = fpriv; + + if (obj->ops && obj->ops->open) { + ret = obj->ops->open(obj, inode, file); + if (ret < 0) + goto done; + } + + spin_lock_irqsave(&obj->file_lock, flags); + list_add_tail(&fpriv->head, &obj->file_list); + spin_unlock_irqrestore(&obj->file_lock, flags); + +done: + if (ret < 0) { + kfree(fpriv); + module_put(obj->parent->ops->owner); + } + return ret; +} + +static int adf_file_release(struct inode *inode, struct file *file) +{ + struct adf_file *fpriv = file->private_data; + struct adf_obj *obj = fpriv->obj; + enum adf_event_type event_type; + unsigned long flags; + + if (obj->ops && obj->ops->release) + obj->ops->release(obj, inode, file); + + spin_lock_irqsave(&obj->file_lock, flags); + list_del(&fpriv->head); + spin_unlock_irqrestore(&obj->file_lock, flags); + + for_each_set_bit(event_type, fpriv->event_subscriptions, + ADF_EVENT_TYPE_MAX) { + adf_event_put(obj, event_type); + } + + kfree(fpriv); + module_put(obj->parent->ops->owner); + + dev_dbg(&obj->dev, "released %s\n", dev_name(&obj->dev)); + return 0; +} + +long adf_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct adf_file *fpriv = file->private_data; + struct adf_obj *obj = fpriv->obj; + long ret = -EINVAL; + + dev_dbg(&obj->dev, "%s ioctl %u\n", dev_name(&obj->dev), _IOC_NR(cmd)); + + switch (obj->type) { + case ADF_OBJ_OVERLAY_ENGINE: + ret = adf_overlay_engine_ioctl(adf_obj_to_overlay_engine(obj), + fpriv, cmd, arg); + break; + + case ADF_OBJ_INTERFACE: + ret = adf_interface_ioctl(adf_obj_to_interface(obj), fpriv, cmd, + arg); + break; + + case ADF_OBJ_DEVICE: + ret = adf_device_ioctl(adf_obj_to_device(obj), fpriv, cmd, arg); + break; + } + + return ret; +} + +static inline bool adf_file_event_available(struct adf_file *fpriv) +{ + int head = fpriv->event_head; + int tail = fpriv->event_tail; + return CIRC_CNT(head, tail, sizeof(fpriv->event_buf)) != 0; +} + +void adf_file_queue_event(struct adf_file *fpriv, struct adf_event *event) +{ + int head = fpriv->event_head; + int tail = fpriv->event_tail; + size_t space = CIRC_SPACE(head, tail, sizeof(fpriv->event_buf)); + size_t space_to_end = + CIRC_SPACE_TO_END(head, tail, sizeof(fpriv->event_buf)); + + if (space < event->length) { + dev_dbg(&fpriv->obj->dev, + "insufficient buffer space for event %u\n", + event->type); + return; + } + + if (space_to_end >= event->length) { + memcpy(fpriv->event_buf + head, event, event->length); + } else { + memcpy(fpriv->event_buf + head, event, space_to_end); + memcpy(fpriv->event_buf, (u8 *)event + space_to_end, + event->length - space_to_end); + } + + smp_wmb(); + fpriv->event_head = (fpriv->event_head + event->length) & + (sizeof(fpriv->event_buf) - 1); + wake_up_interruptible_all(&fpriv->event_wait); +} + +static ssize_t adf_file_copy_to_user(struct adf_file *fpriv, + char __user *buffer, size_t buffer_size) +{ + int head, tail; + u8 *event_buf; + size_t cnt, cnt_to_end, copy_size = 0; + ssize_t ret = 0; + unsigned long flags; + + event_buf = kmalloc(min(buffer_size, sizeof(fpriv->event_buf)), + GFP_KERNEL); + if (!event_buf) + return -ENOMEM; + + spin_lock_irqsave(&fpriv->obj->file_lock, flags); + + if (!adf_file_event_available(fpriv)) + goto out; + + head = fpriv->event_head; + tail = fpriv->event_tail; + + cnt = CIRC_CNT(head, tail, sizeof(fpriv->event_buf)); + cnt_to_end = CIRC_CNT_TO_END(head, tail, sizeof(fpriv->event_buf)); + copy_size = min(buffer_size, cnt); + + if (cnt_to_end >= copy_size) { + memcpy(event_buf, fpriv->event_buf + tail, copy_size); + } else { + memcpy(event_buf, fpriv->event_buf + tail, cnt_to_end); + memcpy(event_buf + cnt_to_end, fpriv->event_buf, + copy_size - cnt_to_end); + } + + fpriv->event_tail = (fpriv->event_tail + copy_size) & + (sizeof(fpriv->event_buf) - 1); + +out: + spin_unlock_irqrestore(&fpriv->obj->file_lock, flags); + if (copy_size) { + if (copy_to_user(buffer, event_buf, copy_size)) + ret = -EFAULT; + else + ret = copy_size; + } + kfree(event_buf); + return ret; +} + +ssize_t adf_file_read(struct file *filp, char __user *buffer, + size_t count, loff_t *offset) +{ + struct adf_file *fpriv = filp->private_data; + int err; + + err = wait_event_interruptible(fpriv->event_wait, + adf_file_event_available(fpriv)); + if (err < 0) + return err; + + return adf_file_copy_to_user(fpriv, buffer, count); +} + +unsigned int adf_file_poll(struct file *filp, struct poll_table_struct *wait) +{ + struct adf_file *fpriv = filp->private_data; + unsigned int mask = 0; + + poll_wait(filp, &fpriv->event_wait, wait); + + if (adf_file_event_available(fpriv)) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +const struct file_operations adf_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = adf_file_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = adf_file_compat_ioctl, +#endif + .open = adf_file_open, + .release = adf_file_release, + .llseek = default_llseek, + .read = adf_file_read, + .poll = adf_file_poll, +}; diff --git a/drivers/video/adf/adf_fops.h b/drivers/video/adf/adf_fops.h new file mode 100644 index 00000000000..90a3a74796d --- /dev/null +++ b/drivers/video/adf/adf_fops.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2013 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 __VIDEO_ADF_ADF_FOPS_H +#define __VIDEO_ADF_ADF_FOPS_H + +#include <linux/bitmap.h> +#include <linux/fs.h> + +extern const struct file_operations adf_fops; + +struct adf_file { + struct list_head head; + struct adf_obj *obj; + + DECLARE_BITMAP(event_subscriptions, ADF_EVENT_TYPE_MAX); + u8 event_buf[4096]; + int event_head; + int event_tail; + wait_queue_head_t event_wait; +}; + +void adf_file_queue_event(struct adf_file *file, struct adf_event *event); +long adf_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg); + +#endif /* __VIDEO_ADF_ADF_FOPS_H */ diff --git a/drivers/video/adf/adf_fops32.c b/drivers/video/adf/adf_fops32.c new file mode 100644 index 00000000000..60a47cf5a78 --- /dev/null +++ b/drivers/video/adf/adf_fops32.c @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2013 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/uaccess.h> +#include <video/adf.h> + +#include "adf_fops.h" +#include "adf_fops32.h" + +long adf_compat_post_config(struct file *file, + struct adf_post_config32 __user *arg) +{ + struct adf_post_config32 cfg32; + struct adf_post_config __user *cfg; + int ret; + + if (copy_from_user(&cfg32, arg, sizeof(cfg32))) + return -EFAULT; + + cfg = compat_alloc_user_space(sizeof(*cfg)); + if (!access_ok(VERIFY_WRITE, cfg, sizeof(*cfg))) + return -EFAULT; + + if (put_user(cfg32.n_interfaces, &cfg->n_interfaces) || + put_user(compat_ptr(cfg32.interfaces), + &cfg->interfaces) || + put_user(cfg32.n_bufs, &cfg->n_bufs) || + put_user(compat_ptr(cfg32.bufs), &cfg->bufs) || + put_user(cfg32.custom_data_size, + &cfg->custom_data_size) || + put_user(compat_ptr(cfg32.custom_data), + &cfg->custom_data)) + return -EFAULT; + + ret = adf_file_ioctl(file, ADF_POST_CONFIG, (unsigned long)cfg); + if (ret < 0) + return ret; + + if (copy_in_user(&arg->complete_fence, &cfg->complete_fence, + sizeof(cfg->complete_fence))) + return -EFAULT; + + return 0; +} + +long adf_compat_get_device_data(struct file *file, + struct adf_device_data32 __user *arg) +{ + struct adf_device_data32 data32; + struct adf_device_data __user *data; + int ret; + + if (copy_from_user(&data32, arg, sizeof(data32))) + return -EFAULT; + + data = compat_alloc_user_space(sizeof(*data)); + if (!access_ok(VERIFY_WRITE, data, sizeof(*data))) + return -EFAULT; + + if (put_user(data32.n_attachments, &data->n_attachments) || + put_user(compat_ptr(data32.attachments), + &data->attachments) || + put_user(data32.n_allowed_attachments, + &data->n_allowed_attachments) || + put_user(compat_ptr(data32.allowed_attachments), + &data->allowed_attachments) || + put_user(data32.custom_data_size, + &data->custom_data_size) || + put_user(compat_ptr(data32.custom_data), + &data->custom_data)) + return -EFAULT; + + ret = adf_file_ioctl(file, ADF_GET_DEVICE_DATA32, (unsigned long)data); + if (ret < 0) + return ret; + + if (copy_in_user(arg->name, data->name, sizeof(arg->name)) || + copy_in_user(&arg->n_attachments, &data->n_attachments, + sizeof(arg->n_attachments)) || + copy_in_user(&arg->n_allowed_attachments, + &data->n_allowed_attachments, + sizeof(arg->n_allowed_attachments)) || + copy_in_user(&arg->custom_data_size, + &data->custom_data_size, + sizeof(arg->custom_data_size))) + return -EFAULT; + + return 0; +} + +long adf_compat_get_interface_data(struct file *file, + struct adf_interface_data32 __user *arg) +{ + struct adf_interface_data32 data32; + struct adf_interface_data __user *data; + int ret; + + if (copy_from_user(&data32, arg, sizeof(data32))) + return -EFAULT; + + data = compat_alloc_user_space(sizeof(*data)); + if (!access_ok(VERIFY_WRITE, data, sizeof(*data))) + return -EFAULT; + + if (put_user(data32.n_available_modes, &data->n_available_modes) || + put_user(compat_ptr(data32.available_modes), + &data->available_modes) || + put_user(data32.custom_data_size, + &data->custom_data_size) || + put_user(compat_ptr(data32.custom_data), + &data->custom_data)) + return -EFAULT; + + ret = adf_file_ioctl(file, ADF_GET_DEVICE_DATA32, (unsigned long)data); + if (ret < 0) + return ret; + + if (copy_in_user(arg->name, data->name, sizeof(arg->name)) || + copy_in_user(&arg->type, &data->type, + sizeof(arg->type)) || + copy_in_user(&arg->id, &data->id, sizeof(arg->id)) || + copy_in_user(&arg->flags, &data->flags, + sizeof(arg->flags)) || + copy_in_user(&arg->dpms_state, &data->dpms_state, + sizeof(arg->dpms_state)) || + copy_in_user(&arg->hotplug_detect, + &data->hotplug_detect, + sizeof(arg->hotplug_detect)) || + copy_in_user(&arg->width_mm, &data->width_mm, + sizeof(arg->width_mm)) || + copy_in_user(&arg->height_mm, &data->height_mm, + sizeof(arg->height_mm)) || + copy_in_user(&arg->current_mode, &data->current_mode, + sizeof(arg->current_mode)) || + copy_in_user(&arg->n_available_modes, + &data->n_available_modes, + sizeof(arg->n_available_modes)) || + copy_in_user(&arg->custom_data_size, + &data->custom_data_size, + sizeof(arg->custom_data_size))) + return -EFAULT; + + return 0; +} + +long adf_compat_get_overlay_engine_data(struct file *file, + struct adf_overlay_engine_data32 __user *arg) +{ + struct adf_overlay_engine_data32 data32; + struct adf_overlay_engine_data __user *data; + int ret; + + if (copy_from_user(&data32, arg, sizeof(data32))) + return -EFAULT; + + data = compat_alloc_user_space(sizeof(*data)); + if (!access_ok(VERIFY_WRITE, data, sizeof(*data))) + return -EFAULT; + + if (put_user(data32.n_supported_formats, &data->n_supported_formats) || + put_user(compat_ptr(data32.supported_formats), + &data->supported_formats) || + put_user(data32.custom_data_size, + &data->custom_data_size) || + put_user(compat_ptr(data32.custom_data), + &data->custom_data)) + return -EFAULT; + + ret = adf_file_ioctl(file, ADF_GET_OVERLAY_ENGINE_DATA, + (unsigned long)data); + if (ret < 0) + return ret; + + if (copy_in_user(arg->name, data->name, sizeof(arg->name)) || + copy_in_user(&arg->n_supported_formats, + &data->n_supported_formats, + sizeof(arg->n_supported_formats)) || + copy_in_user(&arg->custom_data_size, + &data->custom_data_size, + sizeof(arg->custom_data_size))) + return -EFAULT; + + return 0; +} + +long adf_file_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + switch (cmd) { + case ADF_POST_CONFIG32: + return adf_compat_post_config(file, compat_ptr(arg)); + + case ADF_GET_DEVICE_DATA32: + return adf_compat_get_device_data(file, compat_ptr(arg)); + + case ADF_GET_INTERFACE_DATA32: + return adf_compat_get_interface_data(file, compat_ptr(arg)); + + case ADF_GET_OVERLAY_ENGINE_DATA32: + return adf_compat_get_overlay_engine_data(file, + compat_ptr(arg)); + + default: + return adf_file_ioctl(file, cmd, arg); + } +} diff --git a/drivers/video/adf/adf_fops32.h b/drivers/video/adf/adf_fops32.h new file mode 100644 index 00000000000..18c673dc5e2 --- /dev/null +++ b/drivers/video/adf/adf_fops32.h @@ -0,0 +1,78 @@ +#ifndef __VIDEO_ADF_ADF_FOPS32_H +#define __VIDEO_ADF_ADF_FOPS32_H + +#include <linux/compat.h> +#include <linux/ioctl.h> + +#include <video/adf.h> + +#define ADF_POST_CONFIG32 \ + _IOW('D', 2, struct adf_post_config32) +#define ADF_GET_DEVICE_DATA32 \ + _IOR('D', 4, struct adf_device_data32) +#define ADF_GET_INTERFACE_DATA32 \ + _IOR('D', 5, struct adf_interface_data32) +#define ADF_GET_OVERLAY_ENGINE_DATA32 \ + _IOR('D', 6, struct adf_overlay_engine_data32) + +struct adf_post_config32 { + compat_size_t n_interfaces; + compat_uptr_t interfaces; + + compat_size_t n_bufs; + compat_uptr_t bufs; + + compat_size_t custom_data_size; + compat_uptr_t custom_data; + + __s64 complete_fence; +}; + +struct adf_device_data32 { + char name[ADF_NAME_LEN]; + + compat_size_t n_attachments; + compat_uptr_t attachments; + + compat_size_t n_allowed_attachments; + compat_uptr_t allowed_attachments; + + compat_size_t custom_data_size; + compat_uptr_t custom_data; +}; + +struct adf_interface_data32 { + char name[ADF_NAME_LEN]; + + __u8 type; + __u32 id; + /* e.g. type=ADF_INTF_TYPE_DSI, id=1 => DSI.1 */ + __u32 flags; + + __u8 dpms_state; + __u8 hotplug_detect; + __u16 width_mm; + __u16 height_mm; + + struct drm_mode_modeinfo current_mode; + compat_size_t n_available_modes; + compat_uptr_t available_modes; + + compat_size_t custom_data_size; + compat_uptr_t custom_data; +}; + +struct adf_overlay_engine_data32 { + char name[ADF_NAME_LEN]; + + compat_size_t n_supported_formats; + compat_uptr_t supported_formats; + + compat_size_t custom_data_size; + compat_uptr_t custom_data; +}; + +long adf_file_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg); + +#endif /* __VIDEO_ADF_ADF_FOPS32_H */ diff --git a/drivers/video/adf/adf_format.c b/drivers/video/adf/adf_format.c new file mode 100644 index 00000000000..e3f22c7c85d --- /dev/null +++ b/drivers/video/adf/adf_format.c @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2013 Google, Inc. + * modified from drivers/gpu/drm/drm_crtc.c + * + * 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/export.h> +#include <linux/kernel.h> +#include <drm/drm_fourcc.h> +#include <video/adf_format.h> + +bool adf_format_is_standard(u32 format) +{ + switch (format) { + case DRM_FORMAT_C8: + case DRM_FORMAT_RGB332: + case DRM_FORMAT_BGR233: + case DRM_FORMAT_XRGB4444: + case DRM_FORMAT_XBGR4444: + case DRM_FORMAT_RGBX4444: + case DRM_FORMAT_BGRX4444: + case DRM_FORMAT_ARGB4444: + case DRM_FORMAT_ABGR4444: + case DRM_FORMAT_RGBA4444: + case DRM_FORMAT_BGRA4444: + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_XBGR1555: + case DRM_FORMAT_RGBX5551: + case DRM_FORMAT_BGRX5551: + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_BGRA5551: + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + case DRM_FORMAT_RGB888: + case DRM_FORMAT_BGR888: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_BGRA8888: + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_XBGR2101010: + case DRM_FORMAT_RGBX1010102: + case DRM_FORMAT_BGRX1010102: + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_ABGR2101010: + case DRM_FORMAT_RGBA1010102: + case DRM_FORMAT_BGRA1010102: + case DRM_FORMAT_YUYV: + case DRM_FORMAT_YVYU: + case DRM_FORMAT_UYVY: + case DRM_FORMAT_VYUY: + case DRM_FORMAT_AYUV: + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV61: + case DRM_FORMAT_YUV410: + case DRM_FORMAT_YVU410: + case DRM_FORMAT_YUV411: + case DRM_FORMAT_YVU411: + case DRM_FORMAT_YUV420: + case DRM_FORMAT_YVU420: + case DRM_FORMAT_YUV422: + case DRM_FORMAT_YVU422: + case DRM_FORMAT_YUV444: + case DRM_FORMAT_YVU444: + return true; + default: + return false; + } +} +EXPORT_SYMBOL(adf_format_is_standard); + +bool adf_format_is_rgb(u32 format) +{ + switch (format) { + case DRM_FORMAT_C8: + case DRM_FORMAT_RGB332: + case DRM_FORMAT_BGR233: + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_XBGR1555: + case DRM_FORMAT_RGBX5551: + case DRM_FORMAT_BGRX5551: + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_BGRA5551: + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + case DRM_FORMAT_RGB888: + case DRM_FORMAT_BGR888: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_XBGR2101010: + case DRM_FORMAT_RGBX1010102: + case DRM_FORMAT_BGRX1010102: + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_ABGR2101010: + case DRM_FORMAT_RGBA1010102: + case DRM_FORMAT_BGRA1010102: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_BGRA8888: + return true; + + default: + return false; + } +} +EXPORT_SYMBOL(adf_format_is_rgb); + +u8 adf_format_num_planes(u32 format) +{ + switch (format) { + case DRM_FORMAT_YUV410: + case DRM_FORMAT_YVU410: + case DRM_FORMAT_YUV411: + case DRM_FORMAT_YVU411: + case DRM_FORMAT_YUV420: + case DRM_FORMAT_YVU420: + case DRM_FORMAT_YUV422: + case DRM_FORMAT_YVU422: + case DRM_FORMAT_YUV444: + case DRM_FORMAT_YVU444: + return 3; + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV61: + return 2; + default: + return 1; + } +} +EXPORT_SYMBOL(adf_format_num_planes); + +u8 adf_format_bpp(u32 format) +{ + switch (format) { + case DRM_FORMAT_C8: + case DRM_FORMAT_RGB332: + case DRM_FORMAT_BGR233: + return 8; + + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_XBGR1555: + case DRM_FORMAT_RGBX5551: + case DRM_FORMAT_BGRX5551: + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_BGRA5551: + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + return 16; + + case DRM_FORMAT_RGB888: + case DRM_FORMAT_BGR888: + return 24; + + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_XBGR2101010: + case DRM_FORMAT_RGBX1010102: + case DRM_FORMAT_BGRX1010102: + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_ABGR2101010: + case DRM_FORMAT_RGBA1010102: + case DRM_FORMAT_BGRA1010102: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_BGRA8888: + return 32; + + default: + pr_debug("%s: unsupported pixel format %u\n", __func__, format); + return 0; + } +} +EXPORT_SYMBOL(adf_format_bpp); + +u8 adf_format_plane_cpp(u32 format, int plane) +{ + if (plane >= adf_format_num_planes(format)) + return 0; + + switch (format) { + case DRM_FORMAT_YUYV: + case DRM_FORMAT_YVYU: + case DRM_FORMAT_UYVY: + case DRM_FORMAT_VYUY: + return 2; + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV61: + return plane ? 2 : 1; + case DRM_FORMAT_YUV410: + case DRM_FORMAT_YVU410: + case DRM_FORMAT_YUV411: + case DRM_FORMAT_YVU411: + case DRM_FORMAT_YUV420: + case DRM_FORMAT_YVU420: + case DRM_FORMAT_YUV422: + case DRM_FORMAT_YVU422: + case DRM_FORMAT_YUV444: + case DRM_FORMAT_YVU444: + return 1; + default: + return adf_format_bpp(format) / 8; + } +} +EXPORT_SYMBOL(adf_format_plane_cpp); + +u8 adf_format_horz_chroma_subsampling(u32 format) +{ + switch (format) { + case DRM_FORMAT_YUV411: + case DRM_FORMAT_YVU411: + case DRM_FORMAT_YUV410: + case DRM_FORMAT_YVU410: + return 4; + case DRM_FORMAT_YUYV: + case DRM_FORMAT_YVYU: + case DRM_FORMAT_UYVY: + case DRM_FORMAT_VYUY: + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV61: + case DRM_FORMAT_YUV422: + case DRM_FORMAT_YVU422: + case DRM_FORMAT_YUV420: + case DRM_FORMAT_YVU420: + return 2; + default: + return 1; + } +} +EXPORT_SYMBOL(adf_format_horz_chroma_subsampling); + +u8 adf_format_vert_chroma_subsampling(u32 format) +{ + switch (format) { + case DRM_FORMAT_YUV410: + case DRM_FORMAT_YVU410: + return 4; + case DRM_FORMAT_YUV420: + case DRM_FORMAT_YVU420: + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + return 2; + default: + return 1; + } +} +EXPORT_SYMBOL(adf_format_vert_chroma_subsampling); diff --git a/drivers/video/adf/adf_memblock.c b/drivers/video/adf/adf_memblock.c new file mode 100644 index 00000000000..3c99f27388d --- /dev/null +++ b/drivers/video/adf/adf_memblock.c @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2013 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/dma-buf.h> +#include <linux/highmem.h> +#include <linux/memblock.h> +#include <linux/slab.h> + +struct adf_memblock_pdata { + phys_addr_t base; +}; + +static struct sg_table *adf_memblock_map(struct dma_buf_attachment *attach, + enum dma_data_direction direction) +{ + struct adf_memblock_pdata *pdata = attach->dmabuf->priv; + unsigned long pfn = PFN_DOWN(pdata->base); + struct page *page = pfn_to_page(pfn); + struct sg_table *table; + int ret; + + table = kzalloc(sizeof(*table), GFP_KERNEL); + if (!table) + return ERR_PTR(-ENOMEM); + + ret = sg_alloc_table(table, 1, GFP_KERNEL); + if (ret < 0) + goto err; + + sg_set_page(table->sgl, page, attach->dmabuf->size, 0); + return table; + +err: + kfree(table); + return ERR_PTR(ret); +} + +static void adf_memblock_unmap(struct dma_buf_attachment *attach, + struct sg_table *table, enum dma_data_direction direction) +{ + sg_free_table(table); +} + +static void __init_memblock adf_memblock_release(struct dma_buf *buf) +{ + struct adf_memblock_pdata *pdata = buf->priv; + int err = memblock_free(pdata->base, buf->size); + + if (err < 0) + pr_warn("%s: freeing memblock failed: %d\n", __func__, err); + kfree(pdata); +} + +static void *adf_memblock_do_kmap(struct dma_buf *buf, unsigned long pgoffset, + bool atomic) +{ + struct adf_memblock_pdata *pdata = buf->priv; + unsigned long pfn = PFN_DOWN(pdata->base) + pgoffset; + struct page *page = pfn_to_page(pfn); + + if (atomic) + return kmap_atomic(page); + else + return kmap(page); +} + +static void *adf_memblock_kmap_atomic(struct dma_buf *buf, + unsigned long pgoffset) +{ + return adf_memblock_do_kmap(buf, pgoffset, true); +} + +static void adf_memblock_kunmap_atomic(struct dma_buf *buf, + unsigned long pgoffset, void *vaddr) +{ + kunmap_atomic(vaddr); +} + +static void *adf_memblock_kmap(struct dma_buf *buf, unsigned long pgoffset) +{ + return adf_memblock_do_kmap(buf, pgoffset, false); +} + +static void adf_memblock_kunmap(struct dma_buf *buf, unsigned long pgoffset, + void *vaddr) +{ + kunmap(vaddr); +} + +static int adf_memblock_mmap(struct dma_buf *buf, struct vm_area_struct *vma) +{ + struct adf_memblock_pdata *pdata = buf->priv; + + return remap_pfn_range(vma, vma->vm_start, PFN_DOWN(pdata->base), + vma->vm_end - vma->vm_start, vma->vm_page_prot); +} + +struct dma_buf_ops adf_memblock_ops = { + .map_dma_buf = adf_memblock_map, + .unmap_dma_buf = adf_memblock_unmap, + .release = adf_memblock_release, + .kmap_atomic = adf_memblock_kmap_atomic, + .kunmap_atomic = adf_memblock_kunmap_atomic, + .kmap = adf_memblock_kmap, + .kunmap = adf_memblock_kunmap, + .mmap = adf_memblock_mmap, +}; + +/** + * adf_memblock_export - export a memblock reserved area as a dma-buf + * + * @base: base physical address + * @size: memblock size + * @flags: mode flags for the dma-buf's file + * + * @base and @size must be page-aligned. + * + * Returns a dma-buf on success or ERR_PTR(-errno) on failure. + */ +struct dma_buf *adf_memblock_export(phys_addr_t base, size_t size, int flags) +{ + struct adf_memblock_pdata *pdata; + struct dma_buf *buf; + + if (PAGE_ALIGN(base) != base || PAGE_ALIGN(size) != size) + return ERR_PTR(-EINVAL); + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + pdata->base = base; + buf = dma_buf_export(pdata, &adf_memblock_ops, size, flags); + if (IS_ERR(buf)) + kfree(pdata); + + return buf; +} diff --git a/drivers/video/adf/adf_sysfs.c b/drivers/video/adf/adf_sysfs.c new file mode 100644 index 00000000000..076ccbd0cd8 --- /dev/null +++ b/drivers/video/adf/adf_sysfs.c @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2013 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 <video/adf_client.h> + +#include "adf.h" +#include "adf_fops.h" +#include "adf_sysfs.h" + +static struct class *adf_class; +static int adf_major; +static DEFINE_IDR(adf_minors); + +#define dev_to_adf_interface(p) \ + adf_obj_to_interface(container_of(p, struct adf_obj, dev)) + +static ssize_t dpms_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adf_interface *intf = dev_to_adf_interface(dev); + return scnprintf(buf, PAGE_SIZE, "%u\n", + adf_interface_dpms_state(intf)); +} + +static ssize_t dpms_state_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct adf_interface *intf = dev_to_adf_interface(dev); + u8 dpms_state; + int err; + + err = kstrtou8(buf, 0, &dpms_state); + if (err < 0) + return err; + + err = adf_interface_blank(intf, dpms_state); + if (err < 0) + return err; + + return count; +} + +static ssize_t current_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adf_interface *intf = dev_to_adf_interface(dev); + struct drm_mode_modeinfo mode; + + adf_interface_current_mode(intf, &mode); + + if (mode.name[0]) { + return scnprintf(buf, PAGE_SIZE, "%s\n", mode.name); + } else { + bool interlaced = !!(mode.flags & DRM_MODE_FLAG_INTERLACE); + return scnprintf(buf, PAGE_SIZE, "%ux%u%s\n", mode.hdisplay, + mode.vdisplay, interlaced ? "i" : ""); + } +} + +static ssize_t type_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct adf_interface *intf = dev_to_adf_interface(dev); + return scnprintf(buf, PAGE_SIZE, "%s\n", + adf_interface_type_str(intf)); +} + +static ssize_t vsync_timestamp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adf_interface *intf = dev_to_adf_interface(dev); + ktime_t timestamp; + unsigned long flags; + + read_lock_irqsave(&intf->vsync_lock, flags); + memcpy(×tamp, &intf->vsync_timestamp, sizeof(timestamp)); + read_unlock_irqrestore(&intf->vsync_lock, flags); + + return scnprintf(buf, PAGE_SIZE, "%llu\n", ktime_to_ns(timestamp)); +} + +static ssize_t hotplug_detect_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adf_interface *intf = dev_to_adf_interface(dev); + return scnprintf(buf, PAGE_SIZE, "%u\n", intf->hotplug_detect); +} + +static struct device_attribute adf_interface_attrs[] = { + __ATTR(dpms_state, S_IRUGO|S_IWUSR, dpms_state_show, dpms_state_store), + __ATTR_RO(current_mode), + __ATTR_RO(hotplug_detect), + __ATTR_RO(type), + __ATTR_RO(vsync_timestamp), +}; + +static char *adf_devnode(struct device *dev, umode_t *mode) +{ + return kasprintf(GFP_KERNEL, "adf/%s", dev_name(dev)); +} + +int adf_obj_sysfs_init(struct adf_obj *obj, struct device *parent) +{ + int ret = idr_alloc(&adf_minors, obj, 0, 0, GFP_KERNEL); + if (ret < 0) { + pr_err("%s: allocating adf minor failed: %d\n", __func__, + ret); + return ret; + } + + obj->minor = ret; + obj->dev.parent = parent; + obj->dev.class = adf_class; + obj->dev.devt = MKDEV(adf_major, obj->minor); + + ret = device_register(&obj->dev); + if (ret < 0) { + pr_err("%s: registering adf object failed: %d\n", __func__, + ret); + goto err_device_register; + } + + return 0; + +err_device_register: + idr_remove(&adf_minors, obj->minor); + return ret; +} + +static char *adf_device_devnode(struct device *dev, umode_t *mode, + kuid_t *uid, kgid_t *gid) +{ + struct adf_obj *obj = container_of(dev, struct adf_obj, dev); + return kasprintf(GFP_KERNEL, "adf/%s/device", obj->name); +} + +static char *adf_interface_devnode(struct device *dev, umode_t *mode, + kuid_t *uid, kgid_t *gid) +{ + struct adf_obj *obj = container_of(dev, struct adf_obj, dev); + struct adf_interface *intf = adf_obj_to_interface(obj); + struct adf_device *parent = adf_interface_parent(intf); + return kasprintf(GFP_KERNEL, "adf/%s/interface%d", + parent->base.name, intf->base.id); +} + +static char *adf_overlay_engine_devnode(struct device *dev, umode_t *mode, + kuid_t *uid, kgid_t *gid) +{ + struct adf_obj *obj = container_of(dev, struct adf_obj, dev); + struct adf_overlay_engine *eng = adf_obj_to_overlay_engine(obj); + struct adf_device *parent = adf_overlay_engine_parent(eng); + return kasprintf(GFP_KERNEL, "adf/%s/overlay-engine%d", + parent->base.name, eng->base.id); +} + +static void adf_noop_release(struct device *dev) +{ +} + +static struct device_type adf_device_type = { + .name = "adf_device", + .devnode = adf_device_devnode, + .release = adf_noop_release, +}; + +static struct device_type adf_interface_type = { + .name = "adf_interface", + .devnode = adf_interface_devnode, + .release = adf_noop_release, +}; + +static struct device_type adf_overlay_engine_type = { + .name = "adf_overlay_engine", + .devnode = adf_overlay_engine_devnode, + .release = adf_noop_release, +}; + +int adf_device_sysfs_init(struct adf_device *dev) +{ + dev->base.dev.type = &adf_device_type; + dev_set_name(&dev->base.dev, "%s", dev->base.name); + return adf_obj_sysfs_init(&dev->base, dev->dev); +} + +int adf_interface_sysfs_init(struct adf_interface *intf) +{ + struct adf_device *parent = adf_interface_parent(intf); + size_t i, j; + int ret; + + intf->base.dev.type = &adf_interface_type; + dev_set_name(&intf->base.dev, "%s-interface%d", parent->base.name, + intf->base.id); + + ret = adf_obj_sysfs_init(&intf->base, &parent->base.dev); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(adf_interface_attrs); i++) { + ret = device_create_file(&intf->base.dev, + &adf_interface_attrs[i]); + if (ret < 0) { + dev_err(&intf->base.dev, "creating sysfs attribute %s failed: %d\n", + adf_interface_attrs[i].attr.name, ret); + goto err; + } + } + + return 0; + +err: + for (j = 0; j < i; j++) + device_remove_file(&intf->base.dev, &adf_interface_attrs[j]); + return ret; +} + +int adf_overlay_engine_sysfs_init(struct adf_overlay_engine *eng) +{ + struct adf_device *parent = adf_overlay_engine_parent(eng); + + eng->base.dev.type = &adf_overlay_engine_type; + dev_set_name(&eng->base.dev, "%s-overlay-engine%d", parent->base.name, + eng->base.id); + + return adf_obj_sysfs_init(&eng->base, &parent->base.dev); +} + +struct adf_obj *adf_obj_sysfs_find(int minor) +{ + return idr_find(&adf_minors, minor); +} + +void adf_obj_sysfs_destroy(struct adf_obj *obj) +{ + idr_remove(&adf_minors, obj->minor); + device_unregister(&obj->dev); +} + +void adf_device_sysfs_destroy(struct adf_device *dev) +{ + adf_obj_sysfs_destroy(&dev->base); +} + +void adf_interface_sysfs_destroy(struct adf_interface *intf) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(adf_interface_attrs); i++) + device_remove_file(&intf->base.dev, &adf_interface_attrs[i]); + adf_obj_sysfs_destroy(&intf->base); +} + +void adf_overlay_engine_sysfs_destroy(struct adf_overlay_engine *eng) +{ + adf_obj_sysfs_destroy(&eng->base); +} + +int adf_sysfs_init(void) +{ + struct class *class; + int ret; + + class = class_create(THIS_MODULE, "adf"); + if (IS_ERR(class)) { + ret = PTR_ERR(class); + pr_err("%s: creating class failed: %d\n", __func__, ret); + return ret; + } + + ret = register_chrdev(0, "adf", &adf_fops); + if (ret < 0) { + pr_err("%s: registering device failed: %d\n", __func__, ret); + goto err_chrdev; + } + + class->devnode = adf_devnode; + adf_class = class; + adf_major = ret; + return 0; + +err_chrdev: + class_destroy(adf_class); + return ret; +} + +void adf_sysfs_destroy(void) +{ + idr_destroy(&adf_minors); + class_destroy(adf_class); +} diff --git a/drivers/video/adf/adf_sysfs.h b/drivers/video/adf/adf_sysfs.h new file mode 100644 index 00000000000..0613ac364f8 --- /dev/null +++ b/drivers/video/adf/adf_sysfs.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2013 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 __VIDEO_ADF_ADF_SYSFS_H +#define __VIDEO_ADF_ADF_SYSFS_H + +struct adf_device; +struct adf_interface; +struct adf_overlay_engine; + +int adf_device_sysfs_init(struct adf_device *dev); +void adf_device_sysfs_destroy(struct adf_device *dev); +int adf_interface_sysfs_init(struct adf_interface *intf); +void adf_interface_sysfs_destroy(struct adf_interface *intf); +int adf_overlay_engine_sysfs_init(struct adf_overlay_engine *eng); +void adf_overlay_engine_sysfs_destroy(struct adf_overlay_engine *eng); +struct adf_obj *adf_obj_sysfs_find(int minor); + +int adf_sysfs_init(void); +void adf_sysfs_destroy(void); + +#endif /* __VIDEO_ADF_ADF_SYSFS_H */ diff --git a/drivers/video/adf/adf_trace.h b/drivers/video/adf/adf_trace.h new file mode 100644 index 00000000000..3cb2a84d728 --- /dev/null +++ b/drivers/video/adf/adf_trace.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2013 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. + * + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM adf + +#if !defined(__VIDEO_ADF_ADF_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define __VIDEO_ADF_ADF_TRACE_H + +#include <linux/tracepoint.h> +#include <video/adf.h> + +TRACE_EVENT(adf_event, + TP_PROTO(struct adf_obj *obj, enum adf_event_type type), + TP_ARGS(obj, type), + + TP_STRUCT__entry( + __string(name, obj->name) + __field(enum adf_event_type, type) + __array(char, type_str, 32) + ), + TP_fast_assign( + __assign_str(name, obj->name); + __entry->type = type; + strlcpy(__entry->type_str, adf_event_type_str(obj, type), + sizeof(__entry->type_str)); + ), + TP_printk("obj=%s type=%u (%s)", + __get_str(name), + __entry->type, + __entry->type_str) +); + +TRACE_EVENT(adf_event_enable, + TP_PROTO(struct adf_obj *obj, enum adf_event_type type), + TP_ARGS(obj, type), + + TP_STRUCT__entry( + __string(name, obj->name) + __field(enum adf_event_type, type) + __array(char, type_str, 32) + ), + TP_fast_assign( + __assign_str(name, obj->name); + __entry->type = type; + strlcpy(__entry->type_str, adf_event_type_str(obj, type), + sizeof(__entry->type_str)); + ), + TP_printk("obj=%s type=%u (%s)", + __get_str(name), + __entry->type, + __entry->type_str) +); + +TRACE_EVENT(adf_event_disable, + TP_PROTO(struct adf_obj *obj, enum adf_event_type type), + TP_ARGS(obj, type), + + TP_STRUCT__entry( + __string(name, obj->name) + __field(enum adf_event_type, type) + __array(char, type_str, 32) + ), + TP_fast_assign( + __assign_str(name, obj->name); + __entry->type = type; + strlcpy(__entry->type_str, adf_event_type_str(obj, type), + sizeof(__entry->type_str)); + ), + TP_printk("obj=%s type=%u (%s)", + __get_str(name), + __entry->type, + __entry->type_str) +); + +#endif /* __VIDEO_ADF_ADF_TRACE_H */ + +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH . +#define TRACE_INCLUDE_FILE adf_trace +#include <trace/define_trace.h> diff --git a/include/linux/ion.h b/include/linux/ion.h index 440f4b3b0a4..5771f8c3d3a 100644 --- a/include/linux/ion.h +++ b/include/linux/ion.h @@ -19,7 +19,8 @@ #include <linux/types.h> -struct ion_handle; +typedef int ion_user_handle_t; + /** * enum ion_heap_types - list of all possible types of heaps * @ION_HEAP_TYPE_SYSTEM: memory allocated via vmalloc @@ -63,6 +64,7 @@ enum ion_heap_type { caches must be managed manually */ #ifdef __KERNEL__ +struct ion_handle; struct ion_device; struct ion_heap; struct ion_mapper; @@ -268,7 +270,7 @@ struct ion_allocation_data { size_t align; unsigned int heap_id_mask; unsigned int flags; - struct ion_handle *handle; + ion_user_handle_t handle; }; /** @@ -282,7 +284,7 @@ struct ion_allocation_data { * provides the file descriptor and the kernel returns the handle. */ struct ion_fd_data { - struct ion_handle *handle; + ion_user_handle_t handle; int fd; }; @@ -291,7 +293,7 @@ struct ion_fd_data { * @handle: a handle */ struct ion_handle_data { - struct ion_handle *handle; + ion_user_handle_t handle; }; /** diff --git a/include/uapi/video/adf.h b/include/uapi/video/adf.h new file mode 100644 index 00000000000..2ba345ca458 --- /dev/null +++ b/include/uapi/video/adf.h @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2013 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 _UAPI_VIDEO_ADF_H_ +#define _UAPI_VIDEO_ADF_H_ + +#include <linux/ioctl.h> +#include <linux/types.h> + +#include <drm/drm_fourcc.h> +#include <drm/drm_mode.h> + +#define ADF_NAME_LEN 32 +#define ADF_MAX_CUSTOM_DATA_SIZE PAGE_SIZE + +enum adf_interface_type { + ADF_INTF_DSI = 0, + ADF_INTF_eDP = 1, + ADF_INTF_DPI = 2, + ADF_INTF_VGA = 3, + ADF_INTF_DVI = 4, + ADF_INTF_HDMI = 5, + ADF_INTF_MEMORY = 6, + ADF_INTF_TYPE_DEVICE_CUSTOM = 128, + ADF_INTF_TYPE_MAX = (~(__u32)0), +}; + +#define ADF_INTF_FLAG_PRIMARY (1 << 0) +#define ADF_INTF_FLAG_EXTERNAL (1 << 1) + +enum adf_event_type { + ADF_EVENT_VSYNC = 0, + ADF_EVENT_HOTPLUG = 1, + ADF_EVENT_DEVICE_CUSTOM = 128, + ADF_EVENT_TYPE_MAX = 255, +}; + +/** + * struct adf_set_event - start or stop subscribing to ADF events + * + * @type: the type of event to (un)subscribe + * @enabled: subscribe or unsubscribe + * + * After subscribing to an event, userspace may poll() the ADF object's fd + * to wait for events or read() to consume the event's data. + * + * ADF reserves event types 0 to %ADF_EVENT_DEVICE_CUSTOM-1 for its own events. + * Devices may use event types %ADF_EVENT_DEVICE_CUSTOM to %ADF_EVENT_TYPE_MAX-1 + * for driver-private events. + */ +struct adf_set_event { + __u8 type; + __u8 enabled; +}; + +/** + * struct adf_event - common header for ADF event data + * + * @type: event type + * @length: total size of event data, header inclusive + */ +struct adf_event { + __u8 type; + __u32 length; +}; + +/** + * struct adf_vsync_event - ADF vsync event + * + * @base: event header (see &struct adf_event) + * @timestamp: time of vsync event, in nanoseconds + */ +struct adf_vsync_event { + struct adf_event base; + __u64 timestamp; +}; + +/** + * struct adf_vsync_event - ADF display hotplug event + * + * @base: event header (see &struct adf_event) + * @connected: whether a display is now connected to the interface + */ +struct adf_hotplug_event { + struct adf_event base; + __u8 connected; +}; + +#define ADF_MAX_PLANES 4 +/** + * struct adf_buffer_config - description of buffer displayed by adf_post_config + * + * @overlay_engine: id of the target overlay engine + * @w: width of display region in pixels + * @h: height of display region in pixels + * @format: DRM-style fourcc, see drm_fourcc.h for standard formats + * @fd: dma_buf fd for each plane + * @offset: location of first pixel to scan out, in bytes + * @pitch: stride (i.e. length of a scanline including padding) in bytes + * @n_planes: number of planes in buffer + * @acquire_fence: sync_fence fd which will clear when the buffer is + * ready for display, or <0 if the buffer is already ready + */ +struct adf_buffer_config { + __u32 overlay_engine; + + __u32 w; + __u32 h; + __u32 format; + + __s64 fd[ADF_MAX_PLANES]; + __u32 offset[ADF_MAX_PLANES]; + __u32 pitch[ADF_MAX_PLANES]; + __u8 n_planes; + + __s64 acquire_fence; +}; +#define ADF_MAX_BUFFERS (PAGE_SIZE / sizeof(struct adf_buffer_config)) + +/** + * struct adf_post_config - request to flip to a new set of buffers + * + * @n_interfaces: number of interfaces targeted by the flip (input) + * @interfaces: ids of interfaces targeted by the flip (input) + * @n_bufs: number of buffers displayed (input) + * @bufs: description of buffers displayed (input) + * @custom_data_size: size of driver-private data (input) + * @custom_data: driver-private data (input) + * @complete_fence: sync_fence fd which will clear when this + * configuration has left the screen (output) + */ +struct adf_post_config { + size_t n_interfaces; + __u32 __user *interfaces; + + size_t n_bufs; + struct adf_buffer_config __user *bufs; + + size_t custom_data_size; + void __user *custom_data; + + __s64 complete_fence; +}; +#define ADF_MAX_INTERFACES (PAGE_SIZE / sizeof(__u32)) + +/** + * struct adf_simple_buffer_allocate - request to allocate a "simple" buffer + * + * @w: width of buffer in pixels (input) + * @h: height of buffer in pixels (input) + * @format: DRM-style fourcc (input) + * + * @fd: dma_buf fd (output) + * @offset: location of first pixel, in bytes (output) + * @pitch: length of a scanline including padding, in bytes (output) + * + * Simple buffers are analogous to DRM's "dumb" buffers. They have a single + * plane of linear RGB data which can be allocated and scanned out without + * any driver-private ioctls or data. + * + * @format must be a standard RGB format defined in drm_fourcc.h. + * + * ADF clients must NOT assume that an interface can scan out a simple buffer + * allocated by a different ADF interface, even if the two interfaces belong to + * the same ADF device. + */ +struct adf_simple_buffer_alloc { + __u16 w; + __u16 h; + __u32 format; + + __s64 fd; + __u32 offset; + __u32 pitch; +}; + +/** + * struct adf_simple_post_config - request to flip to a single buffer without + * driver-private data + * + * @buf: description of buffer displayed (input) + * @complete_fence: sync_fence fd which will clear when this buffer has left the + * screen (output) + */ +struct adf_simple_post_config { + struct adf_buffer_config buf; + __s64 complete_fence; +}; + +/** + * struct adf_attachment_config - description of attachment between an overlay + * engine and an interface + * + * @overlay_engine: id of the overlay engine + * @interface: id of the interface + */ +struct adf_attachment_config { + __u32 overlay_engine; + __u32 interface; +}; + +/** + * struct adf_device_data - describes a display device + * + * @name: display device's name + * @n_attachments: the number of current attachments + * @attachments: list of current attachments + * @n_allowed_attachments: the number of allowed attachments + * @allowed_attachments: list of allowed attachments + * @custom_data_size: size of driver-private data + * @custom_data: driver-private data + */ +struct adf_device_data { + char name[ADF_NAME_LEN]; + + size_t n_attachments; + struct adf_attachment_config __user *attachments; + + size_t n_allowed_attachments; + struct adf_attachment_config __user *allowed_attachments; + + size_t custom_data_size; + void __user *custom_data; +}; +#define ADF_MAX_ATTACHMENTS (PAGE_SIZE / sizeof(struct adf_attachment)) + +/** + * struct adf_device_data - describes a display interface + * + * @name: display interface's name + * @type: interface type (see enum @adf_interface_type) + * @id: which interface of type @type; + * e.g. interface DSI.1 -> @type=@ADF_INTF_TYPE_DSI, @id=1 + * @flags: informational flags (bitmask of %ADF_INTF_FLAG_* values) + * @dpms_state: DPMS state (one of @DRM_MODE_DPMS_* defined in drm_mode.h) + * @hotplug_detect: whether a display is plugged in + * @width_mm: screen width in millimeters, or 0 if unknown + * @height_mm: screen height in millimeters, or 0 if unknown + * @current_mode: current display mode + * @n_available_modes: the number of hardware display modes + * @available_modes: list of hardware display modes + * @custom_data_size: size of driver-private data + * @custom_data: driver-private data + */ +struct adf_interface_data { + char name[ADF_NAME_LEN]; + + __u32 type; + __u32 id; + /* e.g. type=ADF_INTF_TYPE_DSI, id=1 => DSI.1 */ + __u32 flags; + + __u8 dpms_state; + __u8 hotplug_detect; + __u16 width_mm; + __u16 height_mm; + + struct drm_mode_modeinfo current_mode; + size_t n_available_modes; + struct drm_mode_modeinfo __user *available_modes; + + size_t custom_data_size; + void __user *custom_data; +}; +#define ADF_MAX_MODES (PAGE_SIZE / sizeof(struct drm_mode_modeinfo)) + +/** + * struct adf_overlay_engine_data - describes an overlay engine + * + * @name: overlay engine's name + * @n_supported_formats: number of supported formats + * @supported_formats: list of supported formats + * @custom_data_size: size of driver-private data + * @custom_data: driver-private data + */ +struct adf_overlay_engine_data { + char name[ADF_NAME_LEN]; + + size_t n_supported_formats; + __u32 __user *supported_formats; + + size_t custom_data_size; + void __user *custom_data; +}; +#define ADF_MAX_SUPPORTED_FORMATS (PAGE_SIZE / sizeof(__u32)) + +#define ADF_SET_EVENT _IOW('D', 0, struct adf_set_event) +#define ADF_BLANK _IOW('D', 1, __u8) +#define ADF_POST_CONFIG _IOW('D', 2, struct adf_post_config) +#define ADF_SET_MODE _IOW('D', 3, struct drm_mode_modeinfo) +#define ADF_GET_DEVICE_DATA _IOR('D', 4, struct adf_device_data) +#define ADF_GET_INTERFACE_DATA _IOR('D', 5, struct adf_interface_data) +#define ADF_GET_OVERLAY_ENGINE_DATA \ + _IOR('D', 6, struct adf_overlay_engine_data) +#define ADF_SIMPLE_POST_CONFIG _IOW('D', 7, struct adf_simple_post_config) +#define ADF_SIMPLE_BUFFER_ALLOC _IOW('D', 8, struct adf_simple_buffer_alloc) +#define ADF_ATTACH _IOW('D', 9, struct adf_attachment_config) +#define ADF_DETACH _IOW('D', 10, struct adf_attachment_config) + +#endif /* _UAPI_VIDEO_ADF_H_ */ diff --git a/include/video/adf.h b/include/video/adf.h new file mode 100644 index 00000000000..2b742ab463d --- /dev/null +++ b/include/video/adf.h @@ -0,0 +1,484 @@ +/* + * Copyright (C) 2013 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 _VIDEO_ADF_H +#define _VIDEO_ADF_H + +#include <linux/device.h> +#include <linux/dma-buf.h> +#include <linux/idr.h> +#include <linux/kref.h> +#include <linux/kthread.h> +#include <linux/ktime.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/scatterlist.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/wait.h> +#include <linux/workqueue.h> +#include <uapi/video/adf.h> +#include "sync.h" + +struct adf_obj; +struct adf_obj_ops; +struct adf_device; +struct adf_device_ops; +struct adf_interface; +struct adf_interface_ops; +struct adf_overlay_engine; +struct adf_overlay_engine_ops; + +/** + * struct adf_buffer - buffer displayed by adf_post + * + * @overlay_engine: target overlay engine + * @w: width of display region in pixels + * @h: height of display region in pixels + * @format: DRM-style fourcc, see drm_fourcc.h for standard formats + * @dma_bufs: dma_buf for each plane + * @offset: location of first pixel to scan out, in bytes + * @pitch: length of a scanline including padding, in bytes + * @n_planes: number of planes in buffer + * @acquire_fence: sync_fence which will clear when the buffer is + * ready for display + * + * &struct adf_buffer is the in-kernel counterpart to the userspace-facing + * &struct adf_buffer_config. + */ +struct adf_buffer { + struct adf_overlay_engine *overlay_engine; + + u32 w; + u32 h; + u32 format; + + struct dma_buf *dma_bufs[ADF_MAX_PLANES]; + u32 offset[ADF_MAX_PLANES]; + u32 pitch[ADF_MAX_PLANES]; + u8 n_planes; + + struct sync_fence *acquire_fence; +}; + +/** + * struct adf_buffer_mapping - state for mapping a &struct adf_buffer into the + * display device + * + * @attachments: dma-buf attachment for each plane + * @sg_tables: SG tables for each plane + */ +struct adf_buffer_mapping { + struct dma_buf_attachment *attachments[ADF_MAX_PLANES]; + struct sg_table *sg_tables[ADF_MAX_PLANES]; +}; + +/** + * struct adf_post - request to flip to a new set of buffers + * + * @n_bufs: number of buffers displayed + * @bufs: buffers displayed + * @mappings: in-device mapping state for each buffer + * @custom_data_size: size of driver-private data + * @custom_data: driver-private data + * + * &struct adf_post is the in-kernel counterpart to the userspace-facing + * &struct adf_post_config. + */ +struct adf_post { + size_t n_bufs; + struct adf_buffer *bufs; + struct adf_buffer_mapping *mappings; + + size_t custom_data_size; + void *custom_data; +}; + +/** + * struct adf_attachment - description of attachment between an overlay engine + * and an interface + * + * @overlay_engine: the overlay engine + * @interface: the interface + * + * &struct adf_attachment is the in-kernel counterpart to the userspace-facing + * &struct adf_attachment_config. + */ +struct adf_attachment { + struct adf_overlay_engine *overlay_engine; + struct adf_interface *interface; +}; + +struct adf_pending_post { + struct list_head head; + struct adf_post config; + void *state; +}; + +enum adf_obj_type { + ADF_OBJ_OVERLAY_ENGINE = 0, + ADF_OBJ_INTERFACE = 1, + ADF_OBJ_DEVICE = 2, +}; + +/** + * struct adf_obj_ops - common ADF object implementation ops + * + * @open: handle opening the object's device node + * @release: handle releasing an open file + * @ioctl: handle custom ioctls + * + * @supports_event: return whether the object supports generating events of type + * @type + * @set_event: enable or disable events of type @type + * @event_type_str: return a string representation of custom event @type + * (@type >= %ADF_EVENT_DEVICE_CUSTOM). + * + * @custom_data: copy up to %ADF_MAX_CUSTOM_DATA_SIZE bytes of driver-private + * data into @data (allocated by ADF) and return the number of copied bytes + * in @size. Return 0 on success or an error code (<0) on failure. + */ +struct adf_obj_ops { + /* optional */ + int (*open)(struct adf_obj *obj, struct inode *inode, + struct file *file); + /* optional */ + void (*release)(struct adf_obj *obj, struct inode *inode, + struct file *file); + /* optional */ + long (*ioctl)(struct adf_obj *obj, unsigned int cmd, unsigned long arg); + + /* optional */ + bool (*supports_event)(struct adf_obj *obj, enum adf_event_type type); + /* required if supports_event is implemented */ + void (*set_event)(struct adf_obj *obj, enum adf_event_type type, + bool enabled); + /* optional */ + const char *(*event_type_str)(struct adf_obj *obj, + enum adf_event_type type); + + /* optional */ + int (*custom_data)(struct adf_obj *obj, void *data, size_t *size); +}; + +struct adf_obj { + enum adf_obj_type type; + char name[ADF_NAME_LEN]; + struct adf_device *parent; + + const struct adf_obj_ops *ops; + + struct device dev; + + struct spinlock file_lock; + struct list_head file_list; + + struct mutex event_lock; + struct rb_root event_refcount; + + int id; + int minor; +}; + +/** + * struct adf_device_ops - display device implementation ops + * + * @owner: device's module + * @base: common operations (see &struct adf_obj_ops) + * + * @attach: attach overlay engine @eng to interface @intf. Return 0 on success + * or error code (<0) on failure. + * @detach: detach overlay engine @eng from interface @intf. Return 0 on + * success or error code (<0) on failure. + * + * @validate_custom_format: validate the number and size of planes + * in buffers with a custom format (i.e., not one of the @DRM_FORMAT_* + * types defined in drm/drm_fourcc.h). Return 0 if the buffer is valid or + * an error code (<0) otherwise. + * + * @validate: validate that the proposed configuration @cfg is legal. The + * driver may optionally allocate and return some driver-private state in + * @driver_state, which will be passed to the corresponding post(). The + * driver may NOT commit any changes to hardware. Return 0 if @cfg is + * valid or an error code (<0) otherwise. + * @complete_fence: create a hardware-backed sync fence to be signaled when + * @cfg is removed from the screen. If unimplemented, ADF automatically + * creates an sw_sync fence. Return the sync fence on success or a + * PTR_ERR() on failure. + * @post: flip @cfg onto the screen. Wait for the display to begin scanning out + * @cfg before returning. + * @advance_timeline: signal the sync fence for the last configuration to leave + * the display. If unimplemented, ADF automatically advances an sw_sync + * timeline. + * @state_free: free driver-private state allocated during validate() + */ +struct adf_device_ops { + /* required */ + struct module *owner; + const struct adf_obj_ops base; + + /* optional */ + int (*attach)(struct adf_device *dev, struct adf_overlay_engine *eng, + struct adf_interface *intf); + /* optional */ + int (*detach)(struct adf_device *dev, struct adf_overlay_engine *eng, + struct adf_interface *intf); + + /* required if any of the device's overlay engines supports at least one + custom format */ + int (*validate_custom_format)(struct adf_device *dev, + struct adf_buffer *buf); + + /* required */ + int (*validate)(struct adf_device *dev, struct adf_post *cfg, + void **driver_state); + /* optional */ + struct sync_fence *(*complete_fence)(struct adf_device *dev, + struct adf_post *cfg, void *driver_state); + /* required */ + void (*post)(struct adf_device *dev, struct adf_post *cfg, + void *driver_state); + /* required if complete_fence is implemented */ + void (*advance_timeline)(struct adf_device *dev, + struct adf_post *cfg, void *driver_state); + /* required if validate allocates driver state */ + void (*state_free)(struct adf_device *dev, void *driver_state); +}; + +struct adf_attachment_list { + struct adf_attachment attachment; + struct list_head head; +}; + +struct adf_device { + struct adf_obj base; + struct device *dev; + + const struct adf_device_ops *ops; + + struct mutex client_lock; + + struct idr interfaces; + size_t n_interfaces; + struct idr overlay_engines; + + struct list_head post_list; + struct mutex post_lock; + struct kthread_worker post_worker; + struct task_struct *post_thread; + struct kthread_work post_work; + + struct list_head attached; + size_t n_attached; + struct list_head attach_allowed; + size_t n_attach_allowed; + + struct adf_pending_post *onscreen; + + struct sw_sync_timeline *timeline; + int timeline_max; +}; + +/** + * struct adf_interface_ops - display interface implementation ops + * + * @base: common operations (see &struct adf_obj_ops) + * + * @blank: change the display's DPMS state. Return 0 on success or error + * code (<0) on failure. + * + * @alloc_simple_buffer: allocate a buffer with the specified @w, @h, and + * @format. @format will be a standard RGB format (i.e., + * adf_format_is_rgb(@format) == true). Return 0 on success or error code + * (<0) on failure. On success, return the buffer, offset, and pitch in + * @dma_buf, @offset, and @pitch respectively. + * @describe_simple_post: provide driver-private data needed to post a single + * buffer @buf. Copy up to ADF_MAX_CUSTOM_DATA_SIZE bytes into @data + * (allocated by ADF) and return the number of bytes in @size. Return 0 on + * success or error code (<0) on failure. + * + * @modeset: change the interface's mode. @mode is not necessarily part of the + * modelist passed to adf_hotplug_notify_connected(); the driver may + * accept or reject custom modes at its discretion. Return 0 on success or + * error code (<0) if the mode could not be set. + * + * @screen_size: copy the screen dimensions in millimeters into @width_mm + * and @height_mm. Return 0 on success or error code (<0) if the display + * dimensions are unknown. + * + * @type_str: return a string representation of custom @intf->type + * (@intf->type >= @ADF_INTF_TYPE_DEVICE_CUSTOM). + */ +struct adf_interface_ops { + const struct adf_obj_ops base; + + /* optional */ + int (*blank)(struct adf_interface *intf, u8 state); + + /* optional */ + int (*alloc_simple_buffer)(struct adf_interface *intf, + u16 w, u16 h, u32 format, + struct dma_buf **dma_buf, u32 *offset, u32 *pitch); + /* optional */ + int (*describe_simple_post)(struct adf_interface *intf, + struct adf_buffer *fb, void *data, size_t *size); + + /* optional */ + int (*modeset)(struct adf_interface *intf, + struct drm_mode_modeinfo *mode); + + /* optional */ + int (*screen_size)(struct adf_interface *intf, u16 *width_mm, + u16 *height_mm); + + /* optional */ + const char *(*type_str)(struct adf_interface *intf); +}; + +struct adf_interface { + struct adf_obj base; + const struct adf_interface_ops *ops; + + struct drm_mode_modeinfo current_mode; + + enum adf_interface_type type; + u32 idx; + u32 flags; + + wait_queue_head_t vsync_wait; + ktime_t vsync_timestamp; + rwlock_t vsync_lock; + + u8 dpms_state; + + bool hotplug_detect; + struct drm_mode_modeinfo *modelist; + size_t n_modes; + rwlock_t hotplug_modelist_lock; +}; + +/** + * struct adf_interface_ops - overlay engine implementation ops + * + * @base: common operations (see &struct adf_obj_ops) + * + * @supported_formats: list of fourccs the overlay engine can scan out + * @n_supported_formats: length of supported_formats, up to + * ADF_MAX_SUPPORTED_FORMATS + */ +struct adf_overlay_engine_ops { + const struct adf_obj_ops base; + + /* required */ + const u32 *supported_formats; + /* required */ + const size_t n_supported_formats; +}; + +struct adf_overlay_engine { + struct adf_obj base; + + const struct adf_overlay_engine_ops *ops; +}; + +#define adf_obj_to_device(ptr) \ + container_of((ptr), struct adf_device, base) + +#define adf_obj_to_interface(ptr) \ + container_of((ptr), struct adf_interface, base) + +#define adf_obj_to_overlay_engine(ptr) \ + container_of((ptr), struct adf_overlay_engine, base) + +int __printf(4, 5) adf_device_init(struct adf_device *dev, + struct device *parent, const struct adf_device_ops *ops, + const char *fmt, ...); +void adf_device_destroy(struct adf_device *dev); +int __printf(7, 8) adf_interface_init(struct adf_interface *intf, + struct adf_device *dev, enum adf_interface_type type, u32 idx, + u32 flags, const struct adf_interface_ops *ops, const char *fmt, + ...); +void adf_interface_destroy(struct adf_interface *intf); +static inline struct adf_device *adf_interface_parent( + struct adf_interface *intf) +{ + return intf->base.parent; +} +int __printf(4, 5) adf_overlay_engine_init(struct adf_overlay_engine *eng, + struct adf_device *dev, + const struct adf_overlay_engine_ops *ops, const char *fmt, ...); +void adf_overlay_engine_destroy(struct adf_overlay_engine *eng); +static inline struct adf_device *adf_overlay_engine_parent( + struct adf_overlay_engine *eng) +{ + return eng->base.parent; +} + +int adf_attachment_allow(struct adf_device *dev, struct adf_overlay_engine *eng, + struct adf_interface *intf); + +const char *adf_obj_type_str(enum adf_obj_type type); +const char *adf_interface_type_str(struct adf_interface *intf); +const char *adf_event_type_str(struct adf_obj *obj, enum adf_event_type type); + +#define ADF_FORMAT_STR_SIZE 5 +void adf_format_str(u32 format, char buf[ADF_FORMAT_STR_SIZE]); +int adf_format_validate_yuv(struct adf_device *dev, struct adf_buffer *buf, + u8 num_planes, u8 hsub, u8 vsub, u8 cpp[]); +/** + * adf_format_validate_rgb - validate the number and size of planes in buffers + * with a custom RGB format. + * + * @dev: ADF device performing the validation + * @buf: buffer to validate + * @cpp: expected bytes per pixel + * + * adf_format_validate_rgb() is intended to be called as a helper from @dev's + * validate_custom_format() op. @buf must have a single RGB plane. + * + * Returns 0 if @buf has a single plane with sufficient size, or -EINVAL + * otherwise. + */ +static inline int adf_format_validate_rgb(struct adf_device *dev, + struct adf_buffer *buf, u8 cpp) +{ + return adf_format_validate_yuv(dev, buf, 1, 1, 1, &cpp); +} + +int adf_event_get(struct adf_obj *obj, enum adf_event_type type); +int adf_event_put(struct adf_obj *obj, enum adf_event_type type); +int adf_event_notify(struct adf_obj *obj, struct adf_event *event); + +static inline void adf_vsync_get(struct adf_interface *intf) +{ + adf_event_get(&intf->base, ADF_EVENT_VSYNC); +} + +static inline void adf_vsync_put(struct adf_interface *intf) +{ + adf_event_put(&intf->base, ADF_EVENT_VSYNC); +} + +int adf_vsync_wait(struct adf_interface *intf, long timeout); +void adf_vsync_notify(struct adf_interface *intf, ktime_t timestamp); + +int adf_hotplug_notify_connected(struct adf_interface *intf, + struct drm_mode_modeinfo *modelist, size_t n_modes); +void adf_hotplug_notify_disconnected(struct adf_interface *intf); + +void adf_modeinfo_set_name(struct drm_mode_modeinfo *mode); +void adf_modeinfo_set_vrefresh(struct drm_mode_modeinfo *mode); + +#endif /* _VIDEO_ADF_H */ diff --git a/include/video/adf_client.h b/include/video/adf_client.h new file mode 100644 index 00000000000..983f2b6a589 --- /dev/null +++ b/include/video/adf_client.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2013 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 _VIDEO_ADF_CLIENT_H_ +#define _VIDEO_ADF_CLIENT_H_ + +#include <video/adf.h> + +int adf_interface_blank(struct adf_interface *intf, u8 state); +u8 adf_interface_dpms_state(struct adf_interface *intf); + +void adf_interface_current_mode(struct adf_interface *intf, + struct drm_mode_modeinfo *mode); +size_t adf_interface_modelist(struct adf_interface *intf, + struct drm_mode_modeinfo *modelist, size_t n_modes); +int adf_interface_set_mode(struct adf_interface *intf, + struct drm_mode_modeinfo *mode); +int adf_interface_get_screen_size(struct adf_interface *intf, u16 *width, + u16 *height); +int adf_interface_simple_buffer_alloc(struct adf_interface *intf, u16 w, u16 h, + u32 format, struct dma_buf **dma_buf, u32 *offset, u32 *pitch); +struct sync_fence *adf_interface_simple_post(struct adf_interface *intf, + struct adf_buffer *buf); + +bool adf_overlay_engine_supports_format(struct adf_overlay_engine *eng, + u32 format); + +size_t adf_device_attachments(struct adf_device *dev, + struct adf_attachment *attachments, size_t n_attachments); +size_t adf_device_attachments_allowed(struct adf_device *dev, + struct adf_attachment *attachments, size_t n_attachments); +bool adf_device_attached(struct adf_device *dev, struct adf_overlay_engine *eng, + struct adf_interface *intf); +bool adf_device_attach_allowed(struct adf_device *dev, + struct adf_overlay_engine *eng, struct adf_interface *intf); +int adf_device_attach(struct adf_device *dev, struct adf_overlay_engine *eng, + struct adf_interface *intf); +int adf_device_detach(struct adf_device *dev, struct adf_overlay_engine *eng, + struct adf_interface *intf); + +struct sync_fence *adf_device_post(struct adf_device *dev, + struct adf_interface **intfs, size_t n_intfs, + struct adf_buffer *bufs, size_t n_bufs, void *custom_data, + size_t custom_data_size); +struct sync_fence *adf_device_post_nocopy(struct adf_device *dev, + struct adf_interface **intfs, size_t n_intfs, + struct adf_buffer *bufs, size_t n_bufs, void *custom_data, + size_t custom_data_size); + +#endif /* _VIDEO_ADF_CLIENT_H_ */ diff --git a/include/video/adf_fbdev.h b/include/video/adf_fbdev.h new file mode 100644 index 00000000000..9c349144b5c --- /dev/null +++ b/include/video/adf_fbdev.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2013 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 _VIDEO_ADF_FBDEV_H_ +#define _VIDEO_ADF_FBDEV_H_ + +#include <linux/fb.h> +#include <video/adf.h> + +struct adf_fbdev { + struct adf_interface *intf; + struct adf_overlay_engine *eng; + struct fb_info *info; + u32 pseudo_palette[16]; + + bool open; + + struct dma_buf *dma_buf; + u32 offset; + u32 pitch; + void *vaddr; + u32 format; + + u16 default_xres_virtual; + u16 default_yres_virtual; + u32 default_format; +}; + +void adf_modeinfo_to_fb_videomode(const struct drm_mode_modeinfo *mode, + struct fb_videomode *vmode); +void adf_modeinfo_from_fb_videomode(const struct fb_videomode *vmode, + struct drm_mode_modeinfo *mode); + +int adf_fbdev_init(struct adf_fbdev *fbdev, struct adf_interface *interface, + struct adf_overlay_engine *eng, + u16 xres_virtual, u16 yres_virtual, u32 format, + struct fb_ops *fbops, const char *fmt, ...); +void adf_fbdev_destroy(struct adf_fbdev *fbdev); + +int adf_fbdev_open(struct fb_info *info, int user); +int adf_fbdev_release(struct fb_info *info, int user); +int adf_fbdev_check_var(struct fb_var_screeninfo *var, struct fb_info *info); +int adf_fbdev_set_par(struct fb_info *info); +int adf_fbdev_blank(int blank, struct fb_info *info); +int adf_fbdev_pan_display(struct fb_var_screeninfo *var, struct fb_info *info); +int adf_fbdev_mmap(struct fb_info *info, struct vm_area_struct *vma); + +#endif /* _VIDEO_ADF_FBDEV_H_ */ diff --git a/include/video/adf_format.h b/include/video/adf_format.h new file mode 100644 index 00000000000..e03182cdcb0 --- /dev/null +++ b/include/video/adf_format.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2013 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 _VIDEO_ADF_FORMAT_H +#define _VIDEO_ADF_FORMAT_H + +bool adf_format_is_standard(u32 format); +bool adf_format_is_rgb(u32 format); +u8 adf_format_num_planes(u32 format); +u8 adf_format_bpp(u32 format); +u8 adf_format_plane_cpp(u32 format, int plane); +u8 adf_format_horz_chroma_subsampling(u32 format); +u8 adf_format_vert_chroma_subsampling(u32 format); + +#endif /* _VIDEO_ADF_FORMAT_H */ diff --git a/include/video/adf_memblock.h b/include/video/adf_memblock.h new file mode 100644 index 00000000000..6256e0eebcc --- /dev/null +++ b/include/video/adf_memblock.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2013 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 _VIDEO_ADF_MEMBLOCK_H_ +#define _VIDEO_ADF_MEMBLOCK_H_ + +struct dma_buf *adf_memblock_export(phys_addr_t base, size_t size, int flags); + +#endif /* _VIDEO_ADF_MEMBLOCK_H_ */ diff --git a/mm/page_io.c b/mm/page_io.c index a8a3ef45fed..ba05b64e5d8 100644 --- a/mm/page_io.c +++ b/mm/page_io.c @@ -21,6 +21,7 @@ #include <linux/writeback.h> #include <linux/frontswap.h> #include <linux/aio.h> +#include <linux/blkdev.h> #include <asm/pgtable.h> static struct bio *get_swap_bio(gfp_t gfp_flags, @@ -80,9 +81,54 @@ void end_swap_bio_read(struct bio *bio, int err) imajor(bio->bi_bdev->bd_inode), iminor(bio->bi_bdev->bd_inode), (unsigned long long)bio->bi_sector); - } else { - SetPageUptodate(page); + goto out; } + + SetPageUptodate(page); + + /* + * There is no guarantee that the page is in swap cache - the software + * suspend code (at least) uses end_swap_bio_read() against a non- + * swapcache page. So we must check PG_swapcache before proceeding with + * this optimization. + */ + if (likely(PageSwapCache(page))) { + struct swap_info_struct *sis; + + sis = page_swap_info(page); + if (sis->flags & SWP_BLKDEV) { + /* + * The swap subsystem performs lazy swap slot freeing, + * expecting that the page will be swapped out again. + * So we can avoid an unnecessary write if the page + * isn't redirtied. + * This is good for real swap storage because we can + * reduce unnecessary I/O and enhance wear-leveling + * if an SSD is used as the as swap device. + * But if in-memory swap device (eg zram) is used, + * this causes a duplicated copy between uncompressed + * data in VM-owned memory and compressed data in + * zram-owned memory. So let's free zram-owned memory + * and make the VM-owned decompressed page *dirty*, + * so the page should be swapped out somewhere again if + * we again wish to reclaim it. + */ + struct gendisk *disk = sis->bdev->bd_disk; + if (disk->fops->swap_slot_free_notify) { + swp_entry_t entry; + unsigned long offset; + + entry.val = page_private(page); + offset = swp_offset(entry); + + SetPageDirty(page); + disk->fops->swap_slot_free_notify(sis->bdev, + offset); + } + } + } + +out: unlock_page(page); bio_put(bio); } |