* Re: [PATCHv2 0/7] CGroup Namespaces
From: Aditya Kali @ 2014-12-02 19:14 UTC (permalink / raw)
To: Richard Weinberger
Cc: Linux API, Linux Containers, Serge Hallyn,
linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
Andy Lutomirski, Eric W. Biederman, Tejun Heo,
cgroups mailinglist, Ingo Molnar
In-Reply-To: <CAFLxGvybiem34J3zrtVhW=4itSdczassNt9RcuxnpJQeAz-JVA-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
On Wed, Nov 26, 2014 at 2:58 PM, Richard Weinberger
<richard.weinberger-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
>
> On Thu, Nov 6, 2014 at 6:33 PM, Aditya Kali <adityakali-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org> wrote:
> > On Tue, Nov 4, 2014 at 5:10 AM, Vivek Goyal <vgoyal-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org> wrote:
> >> On Fri, Oct 31, 2014 at 12:18:54PM -0700, Aditya Kali wrote:
> >> [..]
> >>> fs/kernfs/dir.c | 194 ++++++++++++++++++++++++++++++++++-----
> >>> fs/kernfs/mount.c | 48 ++++++++++
> >>> fs/proc/namespaces.c | 1 +
> >>> include/linux/cgroup.h | 41 ++++++++-
> >>> include/linux/cgroup_namespace.h | 36 ++++++++
> >>> include/linux/kernfs.h | 5 +
> >>> include/linux/nsproxy.h | 2 +
> >>> include/linux/proc_ns.h | 4 +
> >>> include/uapi/linux/sched.h | 3 +-
> >>> kernel/Makefile | 2 +-
> >>> kernel/cgroup.c | 108 +++++++++++++++++-----
> >>> kernel/cgroup_namespace.c | 148 +++++++++++++++++++++++++++++
> >>> kernel/fork.c | 2 +-
> >>> kernel/nsproxy.c | 19 +++-
> >>
> >> Hi Aditya,
> >>
> >> Can we provide a documentation file for cgroup namespace behavior. Say,
> >> Documentation/namespaces/cgroup-namespace.txt.
> >>
> > Yes, definitely. I will add it as soon as we have a consensus on the
> > overall series.
>
> Do you have a public git repository which contains your patches?
>
Hi, Sorry for late reply. I don't have these in a public git repo yet.
But I will try to post it on github or somewhere.
Also, I found a bug in this patchset that crashes the kernel in some
cases (when both unified and split hierarchies are mounted). I have a
fix and will send out the patches (with documentation) soon.
>
> --
> Thanks,
> //richard
Thanks,
--
Aditya
^ permalink raw reply
* Re: [PATCH v2] userns: Disallow setgroups unless the gid_map writer is privileged
From: Andy Lutomirski @ 2014-12-02 18:53 UTC (permalink / raw)
To: Eric W. Biederman
Cc: Linux Containers, Josh Triplett, Andrew Morton, Kees Cook,
Michael Kerrisk-manpages, Linux API, linux-man,
linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, LSM,
Casey Schaufler, Serge E. Hallyn, Richard Weinberger,
Kenton Varda, stable
In-Reply-To: <87h9xez20g.fsf-JOvCrm2gF+uungPnsOpG7nhyD016LWXt@public.gmane.org>
On Tue, Dec 2, 2014 at 4:09 AM, Eric W. Biederman <ebiederm-aS9lmoZGLiVWk0Htik3J/w@public.gmane.org> wrote:
> Andy Lutomirski <luto-kltTT9wpgjJwATOyAt5JVQ@public.gmane.org> writes:
>
>> Classic unix permission checks have an interesting feature. The
>> group permissions for a file can be set to less than the other
>> permissions on a file. Occasionally this is used deliberately to
>> give a certain group of users fewer permissions than the default.
>>
>> User namespaces break this usage. Groups set in rgid or egid are
>> unaffected because an unprivileged user namespace creator can only
>> map a single group, so setresgid inside and outside the namespace
>> have the same effect. However, an unprivileged user namespace
>> creator can currently use setgroups(2) to drop all supplementary
>> groups, so, if a supplementary group denies access to some resource,
>> user namespaces can be used to bypass that restriction.
>>
>> To fix this issue, this introduces a new user namespace flag
>> USERNS_SETGROUPS_ALLOWED. If that flag is not set, then
>> setgroups(2) will fail regardless of the caller's capabilities.
>>
>> USERNS_SETGROUPS_ALLOWED is cleared in a new user namespace. By
>> default, if the writer of gid_map has CAP_SETGID in the parent
>> userns and the parent userns has USERNS_SETGROUPS_ALLOWED, then the
>> USERNS_SETGROUPS_ALLOWED will be set in the child. If the writer is
>> not so privileged, then writing to gid_map will fail unless the
>> writer adds "setgroups deny" to gid_map, in which case the check is
>> skipped but USERNS_SETGROUPS_ALLOWED will remain cleared.
>>
>> The full semantics are:
>>
>> If "setgroups allow" is present or no explicit "setgroups" setting
>> is written to gid_map, then writing to gid_map will fail with -EPERM
>> unless the opener and writer have CAP_SETGID in the parent namespace
>> and the parent namespace has USERNS_SETGROUPS_ALLOWED.
>>
>> If "setgroups deny" is present, then writing gid_map will work as
>> before, but USERNS_SETGROUPS_ALLOWED will remain cleared. This will
>> result in processes in the userns that have CAP_SETGID to be
>> nontheless unable to use setgroups(2). If this breaks something
>> inside the userns, then this is okay -- the userns creator
>> specifically requested this behavior.
>
> I think we need to do this but I also think setgroups allow/deny
> should be a separate knob than the uid/gid mapping.
Yeah. It should be readable, too.
>
> If for no other reason than you missed at least two implementations of
> setgroups, in your implementation.
I clearly didn't grep hard enough. Grr.
>
>> While it could be safe to set USERNS_SETGROUPS_ALLOWED if the user
>> namespace creator has no supplementary groups, doing so could be
>> surprising and could have unpleasant interactions with setns(2).
>>
>> Any application that uses newgidmap(1) should be unaffected by this
>> fix, but unprivileged users that create user namespaces to
>> manipulate mounts or sandbox themselves will break until they start
>> using "setgroups deny".
>>
>> This should fix CVE-2014-8989.
>>
>> Cc: stable-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
>> Signed-off-by: Andy Lutomirski <luto-kltTT9wpgjJwATOyAt5JVQ@public.gmane.org>
>> ---
>>
>> Unlike v1, this *will* break things like Sandstorm. Fixing them will be
>> easy. I agree that this will result in better long-term semantics, but
>> I'm not so happy about breaking working software.
>
> I know what you mean. One of the pieces of software broken by all of
> this is my test to verify the remount semantics. Which makes all of
> this very unfortunate.
>
>> If this is unpalatable, here's a different option: get rid of all these
>> permission checks and just change setgroups. Specifically, make it so
>> that setgroups(2) in a userns will succeed but will silently refuse to
>> remove unmapped groups.
>
> Nope silently refusing to remove unmapped groups is not enough. I can
> make any gid in my supplemental groups my egid, it takes a sgid helper
> application but I don't need any special privileges to create that.
> Once that group is my egid I can map it. Which means I could drop
> any one group of my choosing without privielges. Which out and out
> breaks negative groups :(
Whoops, right. And you can, indeed, have egid match one of your
supplementary groups.
>
> I got to looking and I have a significant piece of code that all of this
> breaks.
>
> tools/testing/selftests/mount/unprivileged-remount-test.c
>
> So I am extra motivated to figure out at find a way to preserve most of
> the existing functionality. My regression tests won't pass until I can
> find something pallateable.
>
> It is very annoying that every option I have considered so far breaks
> something useful.
>
> Having a write once setgroups disable, and the allowing unprivileged
> mappings after that seems the most palatable option I have seen,
> semantically. Which means existing software that doesn't care about
> setgroups can just add the disable code and then work otherwise
> unmodified.
>
> The other option that I have played with is forcing a set of groups
> in setgroups if your user namespace was created without privilege,
> that winds up requiring that verify you don't have any other
> supplementary groups, and is generally messy whichever way I look at it.
How bad would the automatic selection of setgroups behavior really be?
Suppose we have /proc/self/userns_setgroups_mode that can be "allow",
"deny", or "auto". It starts out as "auto" (or "deny" if it's set to
"deny" in the parent). Once any of the maps have been set,
userns_options becomes readonly. If you try to write to gid_map when
setgroups == auto, then it switches to "allow" or "deny" depending on
whether the writer has privilege.
This is nasty magical behavior, but it should DTRT for existing users,
and everyone can be updated to set the value explicitly.
FWIW, it might also make sense to move all of this stuff into
/proc/PID/userns. There may be races in which a setuid or otherwise
privileged helper pokes at more than one userns file but actually
modifies different namespaces each time. I don't know whether these
races matter. uid_map, gid_map, and projid_map could be symlinks.
--Andy
>
> *Pounds head on desk*
>
> What a mess.
>
> Eric
>
>> Changes from v1:
>> - Userns flags are now properly atomic.
>> - "setgroups allow" is now the default, so legacy unprivileged gid_map
>> writers will start to fail.
>>
>> include/linux/user_namespace.h | 3 +++
>> kernel/groups.c | 3 +++
>> kernel/user.c | 1 +
>> kernel/user_namespace.c | 42 ++++++++++++++++++++++++++++++++++++++++++
>> 4 files changed, 49 insertions(+)
>>
>> diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h
>> index e95372654f09..0ae4a8c97165 100644
>> --- a/include/linux/user_namespace.h
>> +++ b/include/linux/user_namespace.h
>> @@ -17,6 +17,8 @@ struct uid_gid_map { /* 64 bytes -- 1 cache line */
>> } extent[UID_GID_MAP_MAX_EXTENTS];
>> };
>>
>> +#define USERNS_SETGROUPS_ALLOWED 0
>> +
>> struct user_namespace {
>> struct uid_gid_map uid_map;
>> struct uid_gid_map gid_map;
>> @@ -27,6 +29,7 @@ struct user_namespace {
>> kuid_t owner;
>> kgid_t group;
>> unsigned int proc_inum;
>> + unsigned long flags;
>>
>> /* Register of per-UID persistent keyrings for this namespace */
>> #ifdef CONFIG_PERSISTENT_KEYRINGS
>> diff --git a/kernel/groups.c b/kernel/groups.c
>> index 451698f86cfa..b5ec42423202 100644
>> --- a/kernel/groups.c
>> +++ b/kernel/groups.c
>> @@ -6,6 +6,7 @@
>> #include <linux/slab.h>
>> #include <linux/security.h>
>> #include <linux/syscalls.h>
>> +#include <linux/user_namespace.h>
>> #include <asm/uaccess.h>
>>
>> /* init to 2 - one for init_task, one to ensure it is never freed */
>> @@ -223,6 +224,8 @@ SYSCALL_DEFINE2(setgroups, int, gidsetsize, gid_t __user *, grouplist)
>> struct group_info *group_info;
>> int retval;
>>
>> + if (!test_bit(USERNS_SETGROUPS_ALLOWED, ¤t_user_ns()->flags))
>> + return -EPERM;
>> if (!ns_capable(current_user_ns(), CAP_SETGID))
>> return -EPERM;
>> if ((unsigned)gidsetsize > NGROUPS_MAX)
>> diff --git a/kernel/user.c b/kernel/user.c
>> index 4efa39350e44..58fba8ea0845 100644
>> --- a/kernel/user.c
>> +++ b/kernel/user.c
>> @@ -51,6 +51,7 @@ struct user_namespace init_user_ns = {
>> .owner = GLOBAL_ROOT_UID,
>> .group = GLOBAL_ROOT_GID,
>> .proc_inum = PROC_USER_INIT_INO,
>> + .flags = (1 << USERNS_SETGROUPS_ALLOWED),
>> #ifdef CONFIG_PERSISTENT_KEYRINGS
>> .persistent_keyring_register_sem =
>> __RWSEM_INITIALIZER(init_user_ns.persistent_keyring_register_sem),
>> diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
>> index aa312b0dc3ec..1f63935483e9 100644
>> --- a/kernel/user_namespace.c
>> +++ b/kernel/user_namespace.c
>> @@ -601,6 +601,10 @@ static ssize_t map_write(struct file *file, const char __user *buf,
>> char *kbuf, *pos, *next_line;
>> ssize_t ret = -EINVAL;
>>
>> + bool may_setgroups = false;
>> + bool setgroups_requested = true;
>> + bool seen_explicit_setgroups = false;
>> +
>> /*
>> * The id_map_mutex serializes all writes to any given map.
>> *
>> @@ -633,6 +637,18 @@ static ssize_t map_write(struct file *file, const char __user *buf,
>> if (cap_valid(cap_setid) && !file_ns_capable(file, ns, CAP_SYS_ADMIN))
>> goto out;
>>
>> + if (map == &ns->gid_map) {
>> + /*
>> + * Setgroups is permitted if the writer and the
>> + * parent ns are privileged.
>> + */
>> + may_setgroups =
>> + test_bit(USERNS_SETGROUPS_ALLOWED,
>> + &ns->parent->flags) &&
>> + file_ns_capable(file, ns->parent, CAP_SETGID) &&
>> + ns_capable(ns->parent, CAP_SETGID);
>> + }
>> +
>> /* Get a buffer */
>> ret = -ENOMEM;
>> page = __get_free_page(GFP_TEMPORARY);
>> @@ -667,6 +683,23 @@ static ssize_t map_write(struct file *file, const char __user *buf,
>> next_line = NULL;
>> }
>>
>> + /* Is this line a gid_map option? */
>> + if (map == &ns->gid_map) {
>> + if (!strcmp(pos, "setgroups deny")) {
>> + if (seen_explicit_setgroups)
>> + goto out;
>> + seen_explicit_setgroups = true;
>> + setgroups_requested = false;
>> + continue;
>> + } else if (!strcmp(pos, "setgroups allow")) {
>> + if (seen_explicit_setgroups)
>> + goto out;
>> + seen_explicit_setgroups = true;
>> + setgroups_requested = true;
>> + continue;
>> + }
>> + }
>> +
>> pos = skip_spaces(pos);
>> extent->first = simple_strtoul(pos, &pos, 10);
>> if (!isspace(*pos))
>> @@ -741,6 +774,15 @@ static ssize_t map_write(struct file *file, const char __user *buf,
>> extent->lower_first = lower_first;
>> }
>>
>> + /* Validate and install setgroups permission. */
>> + if (map == &ns->gid_map && setgroups_requested) {
>> + if (!may_setgroups) {
>> + ret = -EPERM;
>> + goto out;
>> + }
>> + set_bit(USERNS_SETGROUPS_ALLOWED, &ns->flags);
>> + }
>> +
>> /* Install the map */
>> memcpy(map->extent, new_map.extent,
>> new_map.nr_extents*sizeof(new_map.extent[0]));
--
Andy Lutomirski
AMA Capital Management, LLC
--
To unsubscribe from this list: send the line "unsubscribe linux-man" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH RFC 1/2] virtio_balloon: convert to virtio 1.0 endian-ness
From: Cornelia Huck @ 2014-12-02 18:39 UTC (permalink / raw)
To: Michael S. Tsirkin
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA,
virtualization-cunTk1MwBs9QetFLy7KEm3xJsTq8ys+cHZ5vskTnxNA
In-Reply-To: <1417520617-2135-1-git-send-email-mst-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
On Tue, 2 Dec 2014 13:44:06 +0200
"Michael S. Tsirkin" <mst-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org> wrote:
> balloon device is not part of virtio 1.0 spec. Still, it's easy enough
> to make it handle endian-ness exactly as other virtio 1.0 devices: what
> we gain from this, is that there's no need to special-case it in virtio
> core.
Well, the balloon is weird in a number of ways, including its always
little-endian config space.
But I'm not quite sure the spec covers this?
>
> Signed-off-by: Michael S. Tsirkin <mst-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> ---
> include/uapi/linux/virtio_balloon.h | 5 +++--
> drivers/virtio/virtio_balloon.c | 4 ++--
> 2 files changed, 5 insertions(+), 4 deletions(-)
>
> struct virtio_balloon_stat {
> - __u16 tag;
> - __u64 val;
> + __virtio16 tag;
> + __virtio64 val;
> } __attribute__((packed));
Would the respective fields in the spec need updating? While it is
actually talking about legacy requirements, the fields are not
specified as __virtio{16,64}.
Also, is changing the stat fields enough? I've not looked into balloon
operation, but does the payload need some endianess conversion as well?
^ permalink raw reply
* Re: Why not make kdbus use CUSE?
From: Greg Kroah-Hartman @ 2014-12-02 17:26 UTC (permalink / raw)
To: Richard Yao
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <547DAEF3.1090106-aBrp7R+bbdUdnm+yROfE0A@public.gmane.org>
On Tue, Dec 02, 2014 at 07:22:11AM -0500, Richard Yao wrote:
> Assuming that this dance succeeds, the FUSE process could then make a
> readonly file in itself, open it read only, unlink it, put the data into
> the file and send the file descriptor via UNIX domain socket while
> refusing further writes. If it has its own user/group, the file should
> be safe from prying eyes.
>
> This is not as good as a memfd and also suffers from the race that
> O_TMPFILE was meant to close, but it should be able to function as a
> decent fallback.
We can't knowingly create and advocate for broken code, sorry.
> This would preserve portability across not only
> different versions of Linux, but also other POSIX systems.
I honestly do not care about any other system than Linux, so I don't see
why this would ever be an issue.
> Keeping the code in userspace would allow us to apply SELinux policies
> to it, which is something that we would lose if it were go to into the
> kernel.
On the contrary, the kdbusfs implementation gives you better security
model support than before, it ties directly into the LSM hooks, see the
add-on patches from some other developers that bring full support of LSM
to the codebase.
> That said, it is still not clear to me that dbus must be inside the
> kernel to be able to perform multicast and zero copy using memfd.
It seems you have yet to read my introductory email for the patch
series.
greg k-h
^ permalink raw reply
* Re: Why not make kdbus use CUSE?
From: Greg Kroah-Hartman @ 2014-12-02 17:12 UTC (permalink / raw)
To: Richard Yao
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <547D7159.2040900-aBrp7R+bbdUdnm+yROfE0A@public.gmane.org>
On Tue, Dec 02, 2014 at 02:59:21AM -0500, Richard Yao wrote:
> On 12/02/2014 12:48 AM, Greg Kroah-Hartman wrote:
> > On Tue, Dec 02, 2014 at 12:40:09AM -0500, Richard Yao wrote:
> >>>> They regard a userland compatibility shim in the systemd repostory to provide
> >>>> backward compatibility for applications. Unfortunately, this is insufficient to
> >>>> ensure compatibility because dependency trees have multiple levels. If cross
> >>>> platform package A depends on cross platform library B, which depends on dbus,
> >>>> and cross platform library B decides to switch to kdbus, then it ceases to be
> >>>> cross platform and cross platform package A is now dependent on Linux kernels
> >>>> with kdbus. Not only does that affect other POSIX systems, but it also affects
> >>>> LTS versions of Linux.
> >>>
> >>> What does LTS versions have anything to do here? And what specific
> >>> dependancies are you worried about?
> >>
> >> Lets say that you have a Linux 3.10 system and you want some package
> >> that indirectly depends on the new API due to library dependencies. You
> >> will have a problem. You could probably install an older version of the
> >> library, but if the older version has a CVE, most end users will end up
> >> between a rock and a hard place. This situation should merit some
> >> consideration because you are taking something that lived previously in
> >> userland, modifying it so that anything depending on the modifications
> >> is no longer backward compatible and then tying it to new kernels.
> >
> > Then you need to get a better distro, as any "well run" long-term
> > enterprise distro handles stuff like this for you. Otherwise you need
> > to update systems properly. There's nothing that I can do here to help
> > with that, nor do I ever want to, sorry.
>
> Another option is to include KVM-style kernel compatibility code to
> allow the module to be built against older kernels. If you target
> 2.6.32.y, 3.2.y, 3.4.y and 3.10.y, the risk of people on older Linux
> systems being left behind would be minimized.
If you want to do that for an out-of-tree patch/module, feel free to do
so, but this has nothing to do with the in-kernel kdbusfs code, sorry.
> >> 1. Debugging kernel code is a pain while debugging user code is
> >> relatively easy.
> >
> > You have full access to a debugger, what more do you need? :)
>
> I would prefer not to start bringing userland daemons into the kernel
> unless there is no other choice. That way, a wider range of people can
> tackle bugs and the code could be applied to a larger number of systems.
What exactly do you mean here? There are thousands of people who know
how to properly debug Linux kernel code, this isn't an issue at all.
> > And why would you need to debug the kernel kdbus code? Is something not
> > working properly in it? Otherwise just use wireshark to read the kdbus
> > data stream and all should be fine.
>
> Putting daemons in the kernel means that we further complicate already
> complex relationships with regard to things like memory utilization and
> CPU time. It is easier to deal with this in userland where we could
> better utilize cgroups.
What does cgroups have to do with dbus userspace libraries here? In
fact, I don't think you looked at the code, as we properly tie into
namespaces and all the stuff you can only do in the kernel, you aren't
reading my introductory email at all that explains all of this.
> >> 2. Security vulnerabilities in kernel code give complete access to
> >> everything while security vulnerabilities in userspace code can be
> >> limited in scope by SELinux.
> >
> > Kernel code is hard, security matters, yes I know this, we all have been
> > doing this for a very long time. Of course bugs happen, but if you look
> > closely, your "attack surface" is now smaller using kdbus than it was
> > using old-style dbus.
>
> Lets say that I have a system running LXC containers, someone does full
> disclosure of proof of concept code for an arbitrary code execution zero
> day and then someone else tries the exploit in a LXC container on
> mysystem. With old-style dbus, only the container is affected and if
> selinux is used, then it is possible to restrict daemon to things in the
> container using dbus.
And how exactly does this relate to the kdbusfs code? Please, stop
making random statements that have nothing to do with the code being
proposed.
> I heard quite clearly at LinuxCon Europe that there are no expected
> benefits from using the shim with kdbus such that we have the equivalent
> of the original dbus daemon in the kernel, but there were plenty of
> benefits from the protocol. If that is the case, it seems that being in
> the kernel is not a necessity, but the new protocol is. FUSE might be
> somewhat slower than an in-kernel filesystem, but it allows us to
> enforce least privilege like we can do now with the current dbus daemon.
> We cannot do that with kdbus/kdbusfs. If the reduction in context switch
> overhead actually mattered, I would understand the desire to put this
> into the kernel, but I have heard quite consistently that the context
> switch overhead is not a significant motivation for pushing this code
> into the kernel.
You heard wrong, the context switch removal is a big thing, and a major
issue for a lot of users. But that's not the only reason this is being
proposed, again, go read and respond to the 00 patch introduction
please, or even better yet, read the code and documentation and respond
to issues you find there.
Again, FUSE makes no sense here, sorry.
> >> 3. Integration with things like LXC should be easier from userspace,
> >> where each container can have its own daemon.
> >
> > How does the current implementation not work properly for this? The
> > filesystem implementation makes this easier than ever, while sticking
> > with the character device made this quite difficult in different ways.
>
> As you pointed out, my information was out of date. Making this into a
> filesystem is an excellent idea that handles container integration quite
> nicely.
I'm glad you agree with the current implementation, thanks for your
approval.
greg k-h
^ permalink raw reply
* Re: Minimal effort/low overhead file descriptor duplication over Posix.1b s
From: Alex Dubov @ 2014-12-02 16:15 UTC (permalink / raw)
To: Jonathan Corbet; +Cc: linux-kernel@vger.kernel.org, linux-api
In-Reply-To: <20141202102632.6ae37b88@lwn.net>
On Wed, Dec 3, 2014 at 2:26 AM, Jonathan Corbet <corbet@lwn.net> wrote:
> On Tue, 2 Dec 2014 15:35:17 +1100
> Alex Dubov <alex.dubov@gmail.com> wrote:
>
>
> - Messing with another process's file descriptor table without its
> knowledge looks like a possible source of all kinds problems. Might
> there be race conditions with close()/dup() code, for example? And
> remember that users can be root in a user namespace; maybe there's no
> potential for mischief there, but it needs to be considered.
If process A has sufficient permissions to signal process B, it can
already do arbitrary mischief, no news there (SIGKILL and SIGSTOP will
definitely cause more havoc :-).
I don't believe there can be any race conditions as this is not
different to what happens when dup() is invoked from one of the
threads in multi-threaded application, whereupon other threads go on
with their usual file operations. Descriptor duplication happens prior
to any signal handling activities.
> - Forcing the use of realtime signals seems strange; this isn't a
> realtime operation by any stretch.
"Real time signals" are merely a misleading name for Posix.1b
micro-messaging facility. To the best of my knowledge they do not
affect scheduling any more then SIGIO or SIGALRM would.
As Posix.1b signals are best handled by signalfd() facility anyway, no
impact on scheduling compared to any other approach (including the
existing domain socket approach) is expected at all.
>
> - How might the sending process communicate to the recipient what the fd
> is for? Even if a process only expects one type of file descriptor,
> the ability to communicate information other than its number seems
> like it would often be useful.
There are 32 "real time" signals defined by default in kernel; this
range can be increased at will with kernel recompilation and glibc
will pick up the correct range automatically (this is Posix mandated
behavior and it actually works like that).
I have not seen an app yet that relied on more than half a dozen of
distinct signal numbers. Thus any application can conveniently define
more than 2 dozens of different fd varieties out of the box, delivered
to it with dedicated signal ids, whereupon in most practical
applications only 1 or 2 varieties of file descriptors are ever passed
around.
>
> Some of these concerns might be addressable by requiring the recipient to
> call acceptfd() (or some such) with the ability to use poll(). As an
> alternative, I believe kdbus has fd-passing abilities; if kdbus goes in,
> would you still need this feature?
Any process willing to handle Posix.1b signals must explicitly
manipulate the signal masks - otherwise it will be killed the moment
signal is received. Thus, no special "acceptfd()" call is necessary on
the receiver side - applications usually don't modify their signal
masks unless they expect some particular signal to arrive.
kdbus has something like it and binder on android has it as well. The
problem with both of them are the same as with unix domain sockets
(which implement a whole, rather convoluted, cmsg facility to be ever
used for that single purpose): they try to solve big problems with
fancy functionality, whereupon fd passing is a nice side feature
(which then gets used the most).
To my understanding, commonly used functionality deserves to have its
own quick, low overhead path:
1. We've got eventfd() which is neat and all, but to use it we need an
easy way to pass its fd around.
2. We've got memfd() which is also neat, but to use it..
3. We've got fairly complex (and consequently buggy) functionality
like SO_REUSEPORT, but I can't avoid a feeling that if there was a low
overhead transport available to path fds around (like the one
proposed), the old school approach of having one process running
tightly around accept() and sending sockets to workers may still rival
it (pity I don't have google's setup around to test it).
4. Most importantly, when network appliances are concerned (and those
represent a huge percentage of linux install base), it is desirable to
have the leanest possible code paths both in kernel and in the user
space (no functionality - no vulnerabilities to fish for) and still be
able to rely on multi-process applications (as multi-process
applications are considerably more reliable then multi-threaded ones,
for all the obvious reasons). A compact, easily traceable facility
comprising few hundred LOCs in the kernel, end to end, and very simple
application code (sigqueue() -> signalfd()) pose a distinct advantage
in this regard over largish subsystems which may provide similar
feature (invariable at the expense of unnecessary costs, like
persistent file system objects, specialized user-space libraries, etc)
.
^ permalink raw reply
* Re: Minimal effort/low overhead file descriptor duplication over Posix.1b s
From: Jonathan Corbet @ 2014-12-02 15:26 UTC (permalink / raw)
To: Alex Dubov; +Cc: linux-kernel, linux-api
In-Reply-To: <1417494919-4577-1-git-send-email-oakad@yahoo.com>
On Tue, 2 Dec 2014 15:35:17 +1100
Alex Dubov <alex.dubov@gmail.com> wrote:
> int sendfd(pid_t pid, int sig, int fd)
>
> Given a target process pid, the sendfd() syscall will create a duplicate
> file descriptor in a target task's (referred by pid) file table pointing to
> the file references by descriptor fd. Then, it will attempt to notify the
> target task by issuing a Posix.1b real-time signal (sig), carrying the new
> file descriptor as integer payload. If real-time signal can not be enqueued
> at the destination signal queue, the newly created file descriptor will be
> promptly closed.
[ CC += linux-api ]
So I'm not a syscall API design expert, but this one raises a few
questions with me.
- Messing with another process's file descriptor table without its
knowledge looks like a possible source of all kinds problems. Might
there be race conditions with close()/dup() code, for example? And
remember that users can be root in a user namespace; maybe there's no
potential for mischief there, but it needs to be considered.
- Forcing the use of realtime signals seems strange; this isn't a
realtime operation by any stretch.
- How might the sending process communicate to the recipient what the fd
is for? Even if a process only expects one type of file descriptor,
the ability to communicate information other than its number seems
like it would often be useful.
Some of these concerns might be addressable by requiring the recipient to
call acceptfd() (or some such) with the ability to use poll(). As an
alternative, I believe kdbus has fd-passing abilities; if kdbus goes in,
would you still need this feature?
Thanks,
jon
^ permalink raw reply
* Re: [PATCH v3] gpio: lib-sysfs: Add 'wakeup' attribute
From: Alexandre Courbot @ 2014-12-02 14:00 UTC (permalink / raw)
To: Sören Brinkmann
Cc: Linus Walleij, Jonathan Corbet, Linux Kernel Mailing List,
linux-api-u79uwXL29TY76Z2rM5mHXA,
linux-gpio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-doc-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
In-Reply-To: <51327750bb694f229ecb1c229cb80b21-reflc3kr++Mk2TTsEbHRquhlVc3/7hDbVaz/vdPVXQ4@public.gmane.org>
On Tue, Dec 2, 2014 at 3:21 AM, Sören Brinkmann
<soren.brinkmann-gjFFaj9aHVfQT0dZR+AlfA@public.gmane.org> wrote:
> Another month past. Any updates on this?
Thanks for pinging. We are just waiting for Linus' call on this - as
far as I am concerned, this looks good.
^ permalink raw reply
* Re: [PATCH 5/7] KVM: arm64: guest debug, add support for single-step
From: Christoffer Dall @ 2014-12-02 13:17 UTC (permalink / raw)
To: Alex Bennée
Cc: kvm-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
kvmarm-FPEHb7Xf0XXUo1n7N8X6UoWGPAHP3yOg, marc.zyngier-5wv7dgnIgG8,
peter.maydell-QSEj5FYQhm4dnm+yROfE0A, agraf-l3A5Bk7waGM,
jan.kiszka-kv7WeFo6aLtBDgjK7y7TUQ,
dahi-23VcF4HTsmIX0ybBhKVfKdBPR1lH4CV8,
r65777-KZfg59tc24xl57MIdRCFDg, bp-l3A5Bk7waGM,
pbonzini-H+wXaHxf7aLQT0dZR+AlfA, Gleb Natapov, Russell King,
Catalin Marinas, Will Deacon, Lorenzo Pieralisi, open list,
open list:ABI/API
In-Reply-To: <87r3wj1te1.fsf-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
On Mon, Dec 01, 2014 at 11:50:14AM +0000, Alex Bennée wrote:
>
> Christoffer Dall <christoffer.dall-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org> writes:
>
> > On Tue, Nov 25, 2014 at 04:10:03PM +0000, Alex Bennée wrote:
> >> This adds support for single-stepping the guest. As userspace can and
> >> will manipulate guest registers before restarting any tweaking of the
> >> registers has to occur just before control is passed back to the guest.
> >> Furthermore while guest debugging is in effect we need to squash the
> >> ability of the guest to single-step itself as we have no easy way of
> >> re-entering the guest after the exception has been delivered to the
> >> hypervisor.
> >
> > Admittedly this is a corner case, but wouldn't the only really nasty bit
> > of this be to emulate the guest debug exception?
>
> Well yes - currently this is all squashed by ignoring the guest's wishes
> while we are debugging (save for SW breakpoints).
>
> >
> >>
> >> Signed-off-by: Alex Bennée <alex.bennee-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
> >>
> >> diff --git a/arch/arm/kvm/arm.c b/arch/arm/kvm/arm.c
> >> index 48d26bb..a76daae 100644
> >> --- a/arch/arm/kvm/arm.c
> >> +++ b/arch/arm/kvm/arm.c
> >> @@ -38,6 +38,7 @@
> >> #include <asm/tlbflush.h>
> >> #include <asm/cacheflush.h>
> >> #include <asm/virt.h>
> >> +#include <asm/debug-monitors.h>
> >> #include <asm/kvm_arm.h>
> >> #include <asm/kvm_asm.h>
> >> #include <asm/kvm_mmu.h>
> >> @@ -300,6 +301,17 @@ void kvm_arch_vcpu_put(struct kvm_vcpu *vcpu)
> >> kvm_arm_set_running_vcpu(NULL);
> >> }
> >>
> >> +/**
> >> + * kvm_arch_vcpu_ioctl_set_guest_debug - Setup guest debugging
> >> + * @kvm: pointer to the KVM struct
> >> + * @kvm_guest_debug: the ioctl data buffer
> >> + *
> >> + * This sets up the VM for guest debugging. Care has to be taken when
> >> + * manipulating guest registers as these will be set/cleared by the
> >> + * hyper-visor controller, typically before each kvm_run event. As a
> >
> > hypervisor
> >
> >> + * result modification of the guest registers needs to take place
> >> + * after they have been restored in the hyp.S trampoline code.
> >
> > I don't understand this??
>
> We can't use GET/SET one reg to manipulate the registers we want as
> these are the guest visible versions and subject to modification by
> userspace. This is why the debugging code makes it's changes after the
> guest state has been restored.
>
eh, once you're in the KVM_RUN ioctl, user space can't fiddle your VCPU
regs because you're holding the vcpu mutex, so doing stuff in some
callout from kvm_arch_vcpu_ioctl_run() seems every bid as valid for this
case as doing it in EL2. In fact, the only reason why we're doing
anything in EL2 is when you're accessing state only accessible in EL2,
when you need to write the whole thing in assembly (like the context
switch of GP registers) etc.
If it doesn't have huge performance costs, we should use C-code in EL1
to the furthest extent possible.
> >
> >> + */
> >> int kvm_arch_vcpu_ioctl_set_guest_debug(struct kvm_vcpu *vcpu,
> >> struct kvm_guest_debug *dbg)
> >> {
> >> @@ -317,8 +329,8 @@ int kvm_arch_vcpu_ioctl_set_guest_debug(struct kvm_vcpu *vcpu,
> >>
> >> /* Single Step */
> >> if (vcpu->guest_debug & KVM_GUESTDBG_SINGLESTEP) {
> >> - kvm_info("SS requested, not yet implemented\n");
> >> - return -EINVAL;
> >> + kvm_info("SS requested\n");
> >> + route_el2 = true;
> >> }
> >>
> >> /* Software Break Points */
> >> diff --git a/arch/arm64/kernel/asm-offsets.c b/arch/arm64/kernel/asm-offsets.c
> >> index 8da1043..78e5ae1 100644
> >> --- a/arch/arm64/kernel/asm-offsets.c
> >> +++ b/arch/arm64/kernel/asm-offsets.c
> >> @@ -121,6 +121,7 @@ int main(void)
> >> DEFINE(VCPU_FAR_EL2, offsetof(struct kvm_vcpu, arch.fault.far_el2));
> >> DEFINE(VCPU_HPFAR_EL2, offsetof(struct kvm_vcpu, arch.fault.hpfar_el2));
> >> DEFINE(VCPU_DEBUG_FLAGS, offsetof(struct kvm_vcpu, arch.debug_flags));
> >> + DEFINE(GUEST_DEBUG, offsetof(struct kvm_vcpu, guest_debug));
> >> DEFINE(VCPU_HCR_EL2, offsetof(struct kvm_vcpu, arch.hcr_el2));
> >> DEFINE(VCPU_MDCR_EL2, offsetof(struct kvm_vcpu, arch.mdcr_el2));
> >> DEFINE(VCPU_IRQ_LINES, offsetof(struct kvm_vcpu, arch.irq_lines));
> >> diff --git a/arch/arm64/kvm/handle_exit.c b/arch/arm64/kvm/handle_exit.c
> >> index 28dc92b..6def054 100644
> >> --- a/arch/arm64/kvm/handle_exit.c
> >> +++ b/arch/arm64/kvm/handle_exit.c
> >> @@ -91,6 +91,25 @@ static int kvm_handle_bkpt(struct kvm_vcpu *vcpu, struct kvm_run *run)
> >> return 0;
> >> }
> >>
> >> +/**
> >> + * kvm_handle_ss - handle single step exceptions
> >> + *
> >> + * @vcpu: the vcpu pointer
> >> + *
> >> + * See: ARM ARM D2.12 for the details. While the host is routing debug
> >> + * exceptions to it's handlers we have to suppress the ability of the
> >
> > its handlers
> >
> >> + * guest to trigger exceptions.
> >
> > not really sure why this comment is here? Does it really help anyone
> > reading this specific function or does it just confuse people more?
> >
> >> + */
> >> +static int kvm_handle_ss(struct kvm_vcpu *vcpu, struct kvm_run *run)
> >> +{
> >> + WARN_ON(!(vcpu->guest_debug & KVM_GUESTDBG_SINGLESTEP));
> >
> > is this something that can actually happen or should it be a BUG_ON() -
> > which may even go away once you're doing hacking on this?
>
> It shouldn't happen. I was treating more like an assert, failure of
> which would indicate something has gone wrong somewhere although
> generally not worth bringing the kernel down for.
>
huh, I guess that's fair enough, but somewhat unconventional for kernel
code. Typically I read WARN_ON (I could be wrong here) as something
that may happen under extreme circumstances (debugging turned on, crazy
low-memory situations etc.), but not as something that catches a bug.
I've seen the argument before that if something that sholdn't ever
happen in the kernel, indeed does happen in the kernel, then that is a
bug, and then you should panic().
So I do feel that this is either a kvm_info()/kvm_err() situation or a
BUG_ON() situation, or nothing at all.
> >
> >> +
> >> + run->exit_reason = KVM_EXIT_DEBUG;
> >> + run->debug.arch.exit_type = KVM_DEBUG_EXIT_SINGLE_STEP;
> >> + run->debug.arch.address = *vcpu_pc(vcpu);
> >> + return 0;
> >> +}
> >> +
> >> static exit_handle_fn arm_exit_handlers[] = {
> >> [ESR_EL2_EC_WFI] = kvm_handle_wfx,
> >> [ESR_EL2_EC_CP15_32] = kvm_handle_cp15_32,
> >> @@ -105,6 +124,7 @@ static exit_handle_fn arm_exit_handlers[] = {
> >> [ESR_EL2_EC_SYS64] = kvm_handle_sys_reg,
> >> [ESR_EL2_EC_IABT] = kvm_handle_guest_abort,
> >> [ESR_EL2_EC_DABT] = kvm_handle_guest_abort,
> >> + [ESR_EL2_EC_SOFTSTP] = kvm_handle_ss,
> >> [ESR_EL2_EC_BKPT32] = kvm_handle_bkpt,
> >> [ESR_EL2_EC_BRK64] = kvm_handle_bkpt,
> >> };
> >> diff --git a/arch/arm64/kvm/hyp.S b/arch/arm64/kvm/hyp.S
> >> index 3c733ea..c0bc218 100644
> >> --- a/arch/arm64/kvm/hyp.S
> >> +++ b/arch/arm64/kvm/hyp.S
> >> @@ -16,6 +16,7 @@
> >> */
> >>
> >> #include <linux/linkage.h>
> >> +#include <linux/kvm.h>
> >>
> >> #include <asm/assembler.h>
> >> #include <asm/memory.h>
> >> @@ -168,6 +169,31 @@
> >> // x19-x29, lr, sp*, elr*, spsr*
> >> restore_common_regs
> >>
> >> + // After restoring the guest registers but before we return to the guest
> >> + // we may want to make some final tweaks to support guest debugging.
> >
> > "we may want" sounds like we're not sure what we'll be doing here. We
> > probably want to write something like "If the guest is being debugged we
> > need to set blah blah blah".
> >
> >> + ldr x3, [x0, #GUEST_DEBUG]
> >> + tbz x3, #KVM_GUESTDBG_ENABLE_SHIFT, 2f // No guest debug
> >> +
> >> + // x0 - preserved as VCPU ptr
> >> + // x1 - spsr
> >> + // x2 - mdscr
> >
> > not sure we need this comment
> >
> >> + mrs x1, spsr_el2
> >> + mrs x2, mdscr_el1
> >> +
> >> + // See ARM ARM D2.12.3 The software step state machine
> >> + // If we are doing Single Step - set MDSCR_EL1.SS and PSTATE.SS
> >> + orr x1, x1, #DBG_SPSR_SS
> >> + orr x2, x2, #DBG_MDSCR_SS
> >> + tbnz x3, #KVM_GUESTDBG_SINGLESTEP_SHIFT, 1f
> >> + // If we are not doing Single Step we want to prevent the guest doing so
> >> + // as otherwise we will have to deal with the re-routed exceptions as we
> >> + // are doing other guest debug related things
> >> + eor x1, x1, #DBG_SPSR_SS
> >> + eor x2, x2, #DBG_MDSCR_SS
> >
> > this really confuses me: so you're setting the SS bits in both
> > registers, and then if we're not single-stepping the guest, you clear
> > both bits again?
> >
> > Wouldn't it be much simper to mask off the bits with a 'bic' and then
> > setting the bits when needed?
>
> Is there a non-vector BIC #imm? I was being frugal with register usage
> at this point. The orr/eor steps where just to avoid having too many
> branch cases.
>
there are "bic x3, x3, #1" and such in this very file, so I would guess,
yes.
> > Alternatively, we could manage all these registers from C code and just
> > save/restore them off the VCPU struct.
>
> Yes but this has to be done as we run into the hyp.S code after all
> guest registers are confirmed as the changes are on-top of whatever the
> guest view is (for the _el1 regs).
>
> Where would you suggest that goes?
>
As a call-out from the arch-specific KVM_RUN ioctl, call
kvm_setup_guest_debug() or something like that, just like we do to check
if our vmid is valid and to setup the vgic state and so on. Does that
work?
-Christoffer
^ permalink raw reply
* Re: [RFC] lsm: namespace hooks
From: Lukasz Pawelczyk @ 2014-12-02 12:43 UTC (permalink / raw)
To: Eric W. Biederman
Cc: Vladimir Davydov, Miklos Szeredi, Lukasz Pawelczyk, Oleg Nesterov,
David Howells, Mark Rustad, Juri Lelli, Richard Weinberger,
Daeseok Youn, Ingo Molnar, Jeff Kirsher, David Rientjes,
Alex Thorlton, Matthew Dempsky, Kees Cook, Nikolay Aleksandrov,
Dario Faggioli, Al Viro, James Morris, open list:ABI/API,
Linux Containers, LKML, Paul Moore
In-Reply-To: <1417109911.1805.27.camel-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
On czw, 2014-11-27 at 18:38 +0100, Lukasz Pawelczyk wrote:
> Right now the major issue I see is that LSM by itself is not defined how
> it's going to behave. It's up to a specific LSM module.
>
> E.g. within the Smack namespace filling the map is a privileged
> operation. So by tying them up you cripple the ability to create a fully
> working user namespace as an unprivileged process.
Entertaining the idea that LSM namespace would be tied to user namespace
(as you suggested) how do you see the limitation I described above?
--
Lukasz Pawelczyk
Samsung R&D Institute Poland
Samsung Electronics
^ permalink raw reply
* Re: Why not make kdbus use CUSE?
From: Richard Yao @ 2014-12-02 12:22 UTC (permalink / raw)
To: Greg Kroah-Hartman; +Cc: linux-kernel, linux-api
In-Reply-To: <547D7159.2040900@gentoo.org>
[-- Attachment #1: Type: text/plain, Size: 2591 bytes --]
Dear Greg,
I had hoped that I could avoid reading through the code for yet another
IPC mechanism when I asked why we needed kdbus at LinuxCon Europe. In
hindsight, I should have just checked out the code and read it instead
of asking. However, what I did instead was ask and then do some thinking
based on that, mean to send an email and then sent it long after I
should have.
Our conversation now has lead me to realize my mistake and I have tried
to rectify it. I now see that kdbus is hooking into kernel interface to
implement KDBUS_ITEM_PAYLOAD_MEMFD in a way that we could only achieve
from userspace with UNIX domain sockets. I imagine that we could avoid
putting this code into the kernel through a combination of libev,
libfuse, memfds and UNIX domain sockets.
A fallback path could be provided by using anonymous files on the FUSE
filesystem. This could probably by by the sender doing something like
the following:
void *buf;
int coookie;
char *name = strdup("/sys/fs/kdbus/tmp/XXXXXX");
int fd = mkstemp(name);
unlink(name);
buf = mmap(NULL, LENGTH, PROT_WRITE, MAP_SHARED, fd, 0);
// Do your writes here
cookie = ioctl(fd, SEAL);
close(fd);
munmap(buf);
// Send a message via a UNIX domain socket to the server with the
// cookie, plus whatever XXXXXX became and instructions on where to
// send the data. If the file had been closed before the message was
// received, the server should be able to say okay. Otherwise, it can //
send an error. The server would need to have a timer to handle the
// case where a process never actually sends a message with the cookie.
Assuming that this dance succeeds, the FUSE process could then make a
readonly file in itself, open it read only, unlink it, put the data into
the file and send the file descriptor via UNIX domain socket while
refusing further writes. If it has its own user/group, the file should
be safe from prying eyes.
This is not as good as a memfd and also suffers from the race that
O_TMPFILE was meant to close, but it should be able to function as a
decent fallback. This would preserve portability across not only
different versions of Linux, but also other POSIX systems. Keeping the
code in userspace would allow us to apply SELinux policies to it, which
is something that we would lose if it were go to into the kernel.
That said, it is still not clear to me that dbus must be inside the
kernel to be able to perform multicast and zero copy using memfd. Is
there something that I have missed that make this not the case?
Yours truly,
Richard Yao
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 884 bytes --]
^ permalink raw reply
* Re: [PATCH v2] userns: Disallow setgroups unless the gid_map writer is privileged
From: Eric W. Biederman @ 2014-12-02 12:09 UTC (permalink / raw)
To: Andy Lutomirski
Cc: linux-man, Kees Cook, Linux API, Linux Containers, Josh Triplett,
stable-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
kenton-AuYgBwuPrUQTaNkGU808tA, LSM, Michael Kerrisk-manpages,
Richard Weinberger, Casey Schaufler, Andrew Morton
In-Reply-To: <52e0643bd47b1e5c65921d6e00aea1f724bb510a.1417281801.git.luto-kltTT9wpgjJwATOyAt5JVQ@public.gmane.org>
Andy Lutomirski <luto-kltTT9wpgjJwATOyAt5JVQ@public.gmane.org> writes:
> Classic unix permission checks have an interesting feature. The
> group permissions for a file can be set to less than the other
> permissions on a file. Occasionally this is used deliberately to
> give a certain group of users fewer permissions than the default.
>
> User namespaces break this usage. Groups set in rgid or egid are
> unaffected because an unprivileged user namespace creator can only
> map a single group, so setresgid inside and outside the namespace
> have the same effect. However, an unprivileged user namespace
> creator can currently use setgroups(2) to drop all supplementary
> groups, so, if a supplementary group denies access to some resource,
> user namespaces can be used to bypass that restriction.
>
> To fix this issue, this introduces a new user namespace flag
> USERNS_SETGROUPS_ALLOWED. If that flag is not set, then
> setgroups(2) will fail regardless of the caller's capabilities.
>
> USERNS_SETGROUPS_ALLOWED is cleared in a new user namespace. By
> default, if the writer of gid_map has CAP_SETGID in the parent
> userns and the parent userns has USERNS_SETGROUPS_ALLOWED, then the
> USERNS_SETGROUPS_ALLOWED will be set in the child. If the writer is
> not so privileged, then writing to gid_map will fail unless the
> writer adds "setgroups deny" to gid_map, in which case the check is
> skipped but USERNS_SETGROUPS_ALLOWED will remain cleared.
>
> The full semantics are:
>
> If "setgroups allow" is present or no explicit "setgroups" setting
> is written to gid_map, then writing to gid_map will fail with -EPERM
> unless the opener and writer have CAP_SETGID in the parent namespace
> and the parent namespace has USERNS_SETGROUPS_ALLOWED.
>
> If "setgroups deny" is present, then writing gid_map will work as
> before, but USERNS_SETGROUPS_ALLOWED will remain cleared. This will
> result in processes in the userns that have CAP_SETGID to be
> nontheless unable to use setgroups(2). If this breaks something
> inside the userns, then this is okay -- the userns creator
> specifically requested this behavior.
I think we need to do this but I also think setgroups allow/deny
should be a separate knob than the uid/gid mapping.
If for no other reason than you missed at least two implementations of
setgroups, in your implementation.
> While it could be safe to set USERNS_SETGROUPS_ALLOWED if the user
> namespace creator has no supplementary groups, doing so could be
> surprising and could have unpleasant interactions with setns(2).
>
> Any application that uses newgidmap(1) should be unaffected by this
> fix, but unprivileged users that create user namespaces to
> manipulate mounts or sandbox themselves will break until they start
> using "setgroups deny".
>
> This should fix CVE-2014-8989.
>
> Cc: stable-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> Signed-off-by: Andy Lutomirski <luto-kltTT9wpgjJwATOyAt5JVQ@public.gmane.org>
> ---
>
> Unlike v1, this *will* break things like Sandstorm. Fixing them will be
> easy. I agree that this will result in better long-term semantics, but
> I'm not so happy about breaking working software.
I know what you mean. One of the pieces of software broken by all of
this is my test to verify the remount semantics. Which makes all of
this very unfortunate.
> If this is unpalatable, here's a different option: get rid of all these
> permission checks and just change setgroups. Specifically, make it so
> that setgroups(2) in a userns will succeed but will silently refuse to
> remove unmapped groups.
Nope silently refusing to remove unmapped groups is not enough. I can
make any gid in my supplemental groups my egid, it takes a sgid helper
application but I don't need any special privileges to create that.
Once that group is my egid I can map it. Which means I could drop
any one group of my choosing without privielges. Which out and out
breaks negative groups :(
I got to looking and I have a significant piece of code that all of this
breaks.
tools/testing/selftests/mount/unprivileged-remount-test.c
So I am extra motivated to figure out at find a way to preserve most of
the existing functionality. My regression tests won't pass until I can
find something pallateable.
It is very annoying that every option I have considered so far breaks
something useful.
Having a write once setgroups disable, and the allowing unprivileged
mappings after that seems the most palatable option I have seen,
semantically. Which means existing software that doesn't care about
setgroups can just add the disable code and then work otherwise
unmodified.
The other option that I have played with is forcing a set of groups
in setgroups if your user namespace was created without privilege,
that winds up requiring that verify you don't have any other
supplementary groups, and is generally messy whichever way I look at it.
*Pounds head on desk*
What a mess.
Eric
> Changes from v1:
> - Userns flags are now properly atomic.
> - "setgroups allow" is now the default, so legacy unprivileged gid_map
> writers will start to fail.
>
> include/linux/user_namespace.h | 3 +++
> kernel/groups.c | 3 +++
> kernel/user.c | 1 +
> kernel/user_namespace.c | 42 ++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 49 insertions(+)
>
> diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h
> index e95372654f09..0ae4a8c97165 100644
> --- a/include/linux/user_namespace.h
> +++ b/include/linux/user_namespace.h
> @@ -17,6 +17,8 @@ struct uid_gid_map { /* 64 bytes -- 1 cache line */
> } extent[UID_GID_MAP_MAX_EXTENTS];
> };
>
> +#define USERNS_SETGROUPS_ALLOWED 0
> +
> struct user_namespace {
> struct uid_gid_map uid_map;
> struct uid_gid_map gid_map;
> @@ -27,6 +29,7 @@ struct user_namespace {
> kuid_t owner;
> kgid_t group;
> unsigned int proc_inum;
> + unsigned long flags;
>
> /* Register of per-UID persistent keyrings for this namespace */
> #ifdef CONFIG_PERSISTENT_KEYRINGS
> diff --git a/kernel/groups.c b/kernel/groups.c
> index 451698f86cfa..b5ec42423202 100644
> --- a/kernel/groups.c
> +++ b/kernel/groups.c
> @@ -6,6 +6,7 @@
> #include <linux/slab.h>
> #include <linux/security.h>
> #include <linux/syscalls.h>
> +#include <linux/user_namespace.h>
> #include <asm/uaccess.h>
>
> /* init to 2 - one for init_task, one to ensure it is never freed */
> @@ -223,6 +224,8 @@ SYSCALL_DEFINE2(setgroups, int, gidsetsize, gid_t __user *, grouplist)
> struct group_info *group_info;
> int retval;
>
> + if (!test_bit(USERNS_SETGROUPS_ALLOWED, ¤t_user_ns()->flags))
> + return -EPERM;
> if (!ns_capable(current_user_ns(), CAP_SETGID))
> return -EPERM;
> if ((unsigned)gidsetsize > NGROUPS_MAX)
> diff --git a/kernel/user.c b/kernel/user.c
> index 4efa39350e44..58fba8ea0845 100644
> --- a/kernel/user.c
> +++ b/kernel/user.c
> @@ -51,6 +51,7 @@ struct user_namespace init_user_ns = {
> .owner = GLOBAL_ROOT_UID,
> .group = GLOBAL_ROOT_GID,
> .proc_inum = PROC_USER_INIT_INO,
> + .flags = (1 << USERNS_SETGROUPS_ALLOWED),
> #ifdef CONFIG_PERSISTENT_KEYRINGS
> .persistent_keyring_register_sem =
> __RWSEM_INITIALIZER(init_user_ns.persistent_keyring_register_sem),
> diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
> index aa312b0dc3ec..1f63935483e9 100644
> --- a/kernel/user_namespace.c
> +++ b/kernel/user_namespace.c
> @@ -601,6 +601,10 @@ static ssize_t map_write(struct file *file, const char __user *buf,
> char *kbuf, *pos, *next_line;
> ssize_t ret = -EINVAL;
>
> + bool may_setgroups = false;
> + bool setgroups_requested = true;
> + bool seen_explicit_setgroups = false;
> +
> /*
> * The id_map_mutex serializes all writes to any given map.
> *
> @@ -633,6 +637,18 @@ static ssize_t map_write(struct file *file, const char __user *buf,
> if (cap_valid(cap_setid) && !file_ns_capable(file, ns, CAP_SYS_ADMIN))
> goto out;
>
> + if (map == &ns->gid_map) {
> + /*
> + * Setgroups is permitted if the writer and the
> + * parent ns are privileged.
> + */
> + may_setgroups =
> + test_bit(USERNS_SETGROUPS_ALLOWED,
> + &ns->parent->flags) &&
> + file_ns_capable(file, ns->parent, CAP_SETGID) &&
> + ns_capable(ns->parent, CAP_SETGID);
> + }
> +
> /* Get a buffer */
> ret = -ENOMEM;
> page = __get_free_page(GFP_TEMPORARY);
> @@ -667,6 +683,23 @@ static ssize_t map_write(struct file *file, const char __user *buf,
> next_line = NULL;
> }
>
> + /* Is this line a gid_map option? */
> + if (map == &ns->gid_map) {
> + if (!strcmp(pos, "setgroups deny")) {
> + if (seen_explicit_setgroups)
> + goto out;
> + seen_explicit_setgroups = true;
> + setgroups_requested = false;
> + continue;
> + } else if (!strcmp(pos, "setgroups allow")) {
> + if (seen_explicit_setgroups)
> + goto out;
> + seen_explicit_setgroups = true;
> + setgroups_requested = true;
> + continue;
> + }
> + }
> +
> pos = skip_spaces(pos);
> extent->first = simple_strtoul(pos, &pos, 10);
> if (!isspace(*pos))
> @@ -741,6 +774,15 @@ static ssize_t map_write(struct file *file, const char __user *buf,
> extent->lower_first = lower_first;
> }
>
> + /* Validate and install setgroups permission. */
> + if (map == &ns->gid_map && setgroups_requested) {
> + if (!may_setgroups) {
> + ret = -EPERM;
> + goto out;
> + }
> + set_bit(USERNS_SETGROUPS_ALLOWED, &ns->flags);
> + }
> +
> /* Install the map */
> memcpy(map->extent, new_map.extent,
> new_map.nr_extents*sizeof(new_map.extent[0]));
^ permalink raw reply
* [PATCH RFC 2/2] virtio_balloon: drop legacy_only
From: Michael S. Tsirkin @ 2014-12-02 11:44 UTC (permalink / raw)
To: linux-kernel; +Cc: linux-api, David Hildenbrand, virtualization
In-Reply-To: <1417520617-2135-1-git-send-email-mst@redhat.com>
balloon is the only driver using legacy_only ATM.
It turns out, it's easier to just make balloon support virtio 1.0
endian-ness and drop the flag from core.
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
---
include/linux/virtio.h | 2 --
drivers/virtio/virtio.c | 4 ----
drivers/virtio/virtio_balloon.c | 1 -
3 files changed, 7 deletions(-)
diff --git a/include/linux/virtio.h b/include/linux/virtio.h
index 2bbf626..f70411e 100644
--- a/include/linux/virtio.h
+++ b/include/linux/virtio.h
@@ -132,7 +132,6 @@ int virtio_device_restore(struct virtio_device *dev);
* @feature_table_size: number of entries in the feature table array.
* @feature_table_legacy: same as feature_table but when working in legacy mode.
* @feature_table_size_legacy: number of entries in feature table legacy array.
- * @legacy_only: driver does not support virtio 1.0.
* @probe: the function to call when a device is found. Returns 0 or -errno.
* @remove: the function to call when a device is removed.
* @config_changed: optional function to call when the device configuration
@@ -145,7 +144,6 @@ struct virtio_driver {
unsigned int feature_table_size;
const unsigned int *feature_table_legacy;
unsigned int feature_table_size_legacy;
- bool legacy_only;
int (*probe)(struct virtio_device *dev);
void (*scan)(struct virtio_device *dev);
void (*remove)(struct virtio_device *dev);
diff --git a/drivers/virtio/virtio.c b/drivers/virtio/virtio.c
index fa6b75d..2e836d8 100644
--- a/drivers/virtio/virtio.c
+++ b/drivers/virtio/virtio.c
@@ -197,10 +197,6 @@ static int virtio_dev_probe(struct device *_d)
driver_features_legacy = driver_features;
}
- /* Detect legacy-only drivers and disable VIRTIO_F_VERSION_1. */
- if (drv->legacy_only)
- device_features &= ~(1ULL << VIRTIO_F_VERSION_1);
-
if (device_features & (1ULL << VIRTIO_F_VERSION_1))
dev->features = driver_features & device_features;
else
diff --git a/drivers/virtio/virtio_balloon.c b/drivers/virtio/virtio_balloon.c
index 721e32f..fed3709 100644
--- a/drivers/virtio/virtio_balloon.c
+++ b/drivers/virtio/virtio_balloon.c
@@ -518,7 +518,6 @@ static unsigned int features[] = {
};
static struct virtio_driver virtio_balloon_driver = {
- .legacy_only = true,
.feature_table = features,
.feature_table_size = ARRAY_SIZE(features),
.driver.name = KBUILD_MODNAME,
--
MST
^ permalink raw reply related
* [PATCH RFC 1/2] virtio_balloon: convert to virtio 1.0 endian-ness
From: Michael S. Tsirkin @ 2014-12-02 11:44 UTC (permalink / raw)
To: linux-kernel; +Cc: linux-api, virtualization
balloon device is not part of virtio 1.0 spec. Still, it's easy enough
to make it handle endian-ness exactly as other virtio 1.0 devices: what
we gain from this, is that there's no need to special-case it in virtio
core.
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
---
include/uapi/linux/virtio_balloon.h | 5 +++--
drivers/virtio/virtio_balloon.c | 4 ++--
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/include/uapi/linux/virtio_balloon.h b/include/uapi/linux/virtio_balloon.h
index 5e26f61..5bee71c 100644
--- a/include/uapi/linux/virtio_balloon.h
+++ b/include/uapi/linux/virtio_balloon.h
@@ -27,6 +27,7 @@
* SUCH DAMAGE. */
#include <linux/virtio_ids.h>
#include <linux/virtio_config.h>
+#include <linux/virtio_types.h>
/* The feature bitmap for virtio balloon */
#define VIRTIO_BALLOON_F_MUST_TELL_HOST 0 /* Tell before reclaiming pages */
@@ -52,8 +53,8 @@ struct virtio_balloon_config
#define VIRTIO_BALLOON_S_NR 6
struct virtio_balloon_stat {
- __u16 tag;
- __u64 val;
+ __virtio16 tag;
+ __virtio64 val;
} __attribute__((packed));
#endif /* _LINUX_VIRTIO_BALLOON_H */
diff --git a/drivers/virtio/virtio_balloon.c b/drivers/virtio/virtio_balloon.c
index 4497def..721e32f 100644
--- a/drivers/virtio/virtio_balloon.c
+++ b/drivers/virtio/virtio_balloon.c
@@ -201,8 +201,8 @@ static inline void update_stat(struct virtio_balloon *vb, int idx,
u16 tag, u64 val)
{
BUG_ON(idx >= VIRTIO_BALLOON_S_NR);
- vb->stats[idx].tag = tag;
- vb->stats[idx].val = val;
+ vb->stats[idx].tag = cpu_to_virtio16(vb->vdev, tag);
+ vb->stats[idx].val = cpu_to_virtio64(vb->vdev, val);
}
#define pages_to_bytes(x) ((u64)(x) << PAGE_SHIFT)
--
MST
^ permalink raw reply related
* Re: [PATCH v17 1/7] mm: support madvise(MADV_FREE)
From: Michal Hocko @ 2014-12-02 10:01 UTC (permalink / raw)
To: Minchan Kim
Cc: Andrew Morton, linux-kernel, linux-mm, Michael Kerrisk, linux-api,
Hugh Dickins, Johannes Weiner, Rik van Riel, KOSAKI Motohiro,
Mel Gorman, Jason Evans, zhangyanfei, Kirill A. Shutemov,
Kirill A. Shutemov
In-Reply-To: <20141130235652.GA10333@bbox>
On Mon 01-12-14 08:56:52, Minchan Kim wrote:
[...]
> From 2edd6890f92fa4943ce3c452194479458582d88c Mon Sep 17 00:00:00 2001
> From: Minchan Kim <minchan@kernel.org>
> Date: Mon, 1 Dec 2014 08:53:55 +0900
> Subject: [PATCH] madvise.2: Document MADV_FREE
>
> Signed-off-by: Minchan Kim <minchan@kernel.org>
> ---
> man2/madvise.2 | 13 +++++++++++++
> 1 file changed, 13 insertions(+)
>
> diff --git a/man2/madvise.2 b/man2/madvise.2
> index 032ead7..33aa936 100644
> --- a/man2/madvise.2
> +++ b/man2/madvise.2
> @@ -265,6 +265,19 @@ file (see
> .BR MADV_DODUMP " (since Linux 3.4)"
> Undo the effect of an earlier
> .BR MADV_DONTDUMP .
> +.TP
> +.BR MADV_FREE " (since Linux 3.19)"
> +Gives the VM system the freedom to free pages, and tells the system that
> +information in the specified page range is no longer important.
> +This is an efficient way of allowing
> +.BR malloc (3)
This might be rather misleading. Only some malloc implementations are
using this feature (jemalloc, right?). So either be specific about which
implementation or do not add it at all.
> +to free pages anywhere in the address space, while keeping the address space
> +valid. The next time that the page is referenced, the page might be demand
> +zeroed, or might contain the data that was there before the MADV_FREE call.
> +References made to that address space range will not make the VM system page the
> +information back in from backing store until the page is modified again.
I am not sure I understand the last sentence. So say I did MADV_FREE and
the reclaim has dropped that page. I know that the file backed mappings
are not supported yet but assume they were for a second... Now, I do
read from that location again what is the result?
If we consider anon mappings then the backing store is misleading as
well because memory was dropped and so always newly allocated.
I would rather drop the whole sentence and rather see an explanation
what is the difference between to MADV_DONT_NEED.
"
Unlike MADV_DONT_NEED the memory is freed lazily e.g. when the VM system
is under memory pressure.
"
> +It works only with private anonymous pages (see
> +.BR mmap (2)).
> .SH RETURN VALUE
> On success
> .BR madvise ()
--
Michal Hocko
SUSE Labs
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
^ permalink raw reply
* [GIT PULL] core drm support for Rockchip SoCs v15
From: Mark yao @ 2014-12-02 9:39 UTC (permalink / raw)
To: heiko-4mtYJXux2i+zQB+pC5nmwQ, Boris BREZILLON, David Airlie,
Rob Clark, Daniel Vetter, Rob Herring, Pawel Moll, Mark Rutland,
Ian Campbell, Kumar Gala, Randy Dunlap, Grant Likely,
Greg Kroah-Hartman, John Stultz, Rom Lemarchand
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
linux-api-u79uwXL29TY76Z2rM5mHXA,
linux-rockchip-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
dianders-F7+t8E8rja9g9hUCZPvPmw, marcheu-F7+t8E8rja9g9hUCZPvPmw,
dbehr-F7+t8E8rja9g9hUCZPvPmw, olof-nZhT3qVonbNeoWH0uzbU5w,
djkurtz-F7+t8E8rja9g9hUCZPvPmw, cf-TNX95d0MmH7DzftRWevZcw,
xxm-TNX95d0MmH7DzftRWevZcw, huangtao-TNX95d0MmH7DzftRWevZcw,
kever.yang-TNX95d0MmH7DzftRWevZcw, yxj-TNX95d0MmH7DzftRWevZcw,
xw-TNX95d0MmH7DzftRWevZcw, Mark Yao
In-Reply-To: <1417511600-7054-1-git-send-email-mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
Hi Dave
The following changes since commit 656d7077d8ffd1c2492d4a0a354367ab2e545059:
dt-bindings: iommu: Add documentation for rockchip iommu (2014-11-03
17:29:09 +0100)
are available in the git repository at:
https://github.com/markyzq/kernel-drm-rockchip.git drm_iommu_v15
for you to fetch changes up to 5ac4837b12f533de5d9f8f66b45494c58e805536:
dt-bindings: video: Add documentation for rockchip vop (2014-12-02
17:29:33 +0800)
----------------------------------------------------------------
Mark Yao (3):
drm: rockchip: Add basic drm driver
dt-bindings: video: Add for rockchip display subsytem
dt-bindings: video: Add documentation for rockchip vop
.../devicetree/bindings/video/rockchip-drm.txt | 19 +
.../devicetree/bindings/video/rockchip-vop.txt | 58 +
drivers/gpu/drm/Kconfig | 2 +
drivers/gpu/drm/Makefile | 1 +
drivers/gpu/drm/rockchip/Kconfig | 17 +
drivers/gpu/drm/rockchip/Makefile | 8 +
drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 551 ++++++++
drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 68 +
drivers/gpu/drm/rockchip/rockchip_drm_fb.c | 201 +++
drivers/gpu/drm/rockchip/rockchip_drm_fb.h | 28 +
drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c | 210 +++
drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h | 21 +
drivers/gpu/drm/rockchip/rockchip_drm_gem.c | 294 ++++
drivers/gpu/drm/rockchip/rockchip_drm_gem.h | 54 +
drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 1455
++++++++++++++++++++
drivers/gpu/drm/rockchip/rockchip_drm_vop.h | 201 +++
16 files changed, 3188 insertions(+)
create mode 100644
Documentation/devicetree/bindings/video/rockchip-drm.txt
create mode 100644
Documentation/devicetree/bindings/video/rockchip-vop.txt
create mode 100644 drivers/gpu/drm/rockchip/Kconfig
create mode 100644 drivers/gpu/drm/rockchip/Makefile
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_drv.c
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_drv.h
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fb.c
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fb.h
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_gem.c
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_gem.h
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_vop.c
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_vop.h
^ permalink raw reply
* Re: [PATCH v15 0/3] Add drm driver for Rockchip Socs
From: Heiko Stübner @ 2014-12-02 9:31 UTC (permalink / raw)
To: Mark Yao
Cc: Boris BREZILLON, David Airlie, Rob Clark, Daniel Vetter,
Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
Randy Dunlap, Grant Likely, Greg Kroah-Hartman, John Stultz,
Rom Lemarchand, devicetree, linux-doc, linux-kernel, dri-devel,
linux-api, linux-rockchip, dianders, marcheu, dbehr, olof,
djkurtz, cf, xxm, huangtao
In-Reply-To: <1417511600-7054-1-git-send-email-mark.yao@rock-chips.com>
Hi Mark,
Am Dienstag, 2. Dezember 2014, 17:13:20 schrieb Mark Yao:
> This a series of patches is a DRM Driver for Rockchip Socs, add support
> for vop devices. Future patches will add additional encoders/connectors,
> such as eDP, HDMI.
>
> The basic "crtc" for rockchip is a "VOP" - Video Output Processor.
> the vop devices found on Rockchip rk3288 Soc, rk3288 soc have two similar
> Vop devices. Vop devices support iommu mapping, we use dma-mapping API with
> ARM_DMA_USE_IOMMU.
[...]
> Changes in v15:
> - remove depends on ARM_DMA_USE_IOMMU & IOMMU_API which cause
> recursive dependency problem
> - fix compile problems when build as a module.
maybe you could also already create a new pull request for this version [like
the last one based on the iommu branch], so that we save time and Dave can
pull it in easily for testing if he likes.
Heiko
^ permalink raw reply
* [PATCH v15 3/3] dt-bindings: video: Add documentation for rockchip vop
From: Mark Yao @ 2014-12-02 9:18 UTC (permalink / raw)
To: heiko-4mtYJXux2i+zQB+pC5nmwQ, Boris BREZILLON, David Airlie,
Rob Clark, Daniel Vetter, Rob Herring, Pawel Moll, Mark Rutland,
Ian Campbell, Kumar Gala, Randy Dunlap, Grant Likely,
Greg Kroah-Hartman, John Stultz, Rom Lemarchand
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
linux-api-u79uwXL29TY76Z2rM5mHXA,
linux-rockchip-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
dianders-F7+t8E8rja9g9hUCZPvPmw, marcheu-F7+t8E8rja9g9hUCZPvPmw,
dbehr-F7+t8E8rja9g9hUCZPvPmw, olof-nZhT3qVonbNeoWH0uzbU5w,
djkurtz-F7+t8E8rja9g9hUCZPvPmw, cf-TNX95d0MmH7DzftRWevZcw,
xxm-TNX95d0MmH7DzftRWevZcw, huangtao-TNX95d0MmH7DzftRWevZcw,
kever.yang-TNX95d0MmH7DzftRWevZcw, yxj-TNX95d0MmH7DzftRWevZcw,
xw-TNX95d0MmH7DzftRWevZcw, Mark Yao
In-Reply-To: <1417511600-7054-1-git-send-email-mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
This adds binding documentation for Rockchip SoC VOP driver.
Signed-off-by: Mark Yao <mark.yao-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
---
Changes in v2:
- rename "lcdc" to "vop"
- add vop reset
- add iommu node
- add port for display-subsystem
Changes in v3: None
Changes in v4: None
Changes in v5: None
Changes in v6: None
Changes in v7: None
Changes in v8: None
Changes in v9: None
Changes in v10: None
Changes in v11: None
Changes in v12: None
Changes in v13: None
Changes in v14: None
Changes in v15: None
.../devicetree/bindings/video/rockchip-vop.txt | 58 ++++++++++++++++++++
1 file changed, 58 insertions(+)
create mode 100644 Documentation/devicetree/bindings/video/rockchip-vop.txt
diff --git a/Documentation/devicetree/bindings/video/rockchip-vop.txt b/Documentation/devicetree/bindings/video/rockchip-vop.txt
new file mode 100644
index 0000000..d15351f
--- /dev/null
+++ b/Documentation/devicetree/bindings/video/rockchip-vop.txt
@@ -0,0 +1,58 @@
+device-tree bindings for rockchip soc display controller (vop)
+
+VOP (Visual Output Processor) is the Display Controller for the Rockchip
+series of SoCs which transfers the image data from a video memory
+buffer to an external LCD interface.
+
+Required properties:
+- compatible: value should be one of the following
+ "rockchip,rk3288-vop";
+
+- interrupts: should contain a list of all VOP IP block interrupts in the
+ order: VSYNC, LCD_SYSTEM. The interrupt specifier
+ format depends on the interrupt controller used.
+
+- clocks: must include clock specifiers corresponding to entries in the
+ clock-names property.
+
+- clock-names: Must contain
+ aclk_vop: for ddr buffer transfer.
+ hclk_vop: for ahb bus to R/W the phy regs.
+ dclk_vop: pixel clock.
+
+- resets: Must contain an entry for each entry in reset-names.
+ See ../reset/reset.txt for details.
+- reset-names: Must include the following entries:
+ - axi
+ - ahb
+ - dclk
+
+- iommus: required a iommu node
+
+- port: A port node with endpoint definitions as defined in
+ Documentation/devicetree/bindings/media/video-interfaces.txt.
+
+Example:
+SoC specific DT entry:
+ vopb: vopb@ff930000 {
+ compatible = "rockchip,rk3288-vop";
+ reg = <0xff930000 0x19c>;
+ interrupts = <GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cru ACLK_VOP0>, <&cru DCLK_VOP0>, <&cru HCLK_VOP0>;
+ clock-names = "aclk_vop", "dclk_vop", "hclk_vop";
+ resets = <&cru SRST_LCDC1_AXI>, <&cru SRST_LCDC1_AHB>, <&cru SRST_LCDC1_DCLK>;
+ reset-names = "axi", "ahb", "dclk";
+ iommus = <&vopb_mmu>;
+ vopb_out: port {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ vopb_out_edp: endpoint@0 {
+ reg = <0>;
+ remote-endpoint=<&edp_in_vopb>;
+ };
+ vopb_out_hdmi: endpoint@1 {
+ reg = <1>;
+ remote-endpoint=<&hdmi_in_vopb>;
+ };
+ };
+ };
--
1.7.9.5
^ permalink raw reply related
* [PATCH v15 2/3] dt-bindings: video: Add for rockchip display subsytem
From: Mark Yao @ 2014-12-02 9:16 UTC (permalink / raw)
To: heiko, Boris BREZILLON, David Airlie, Rob Clark, Daniel Vetter,
Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
Randy Dunlap, Grant Likely, Greg Kroah-Hartman, John Stultz,
Rom Lemarchand
Cc: devicetree, linux-doc, linux-kernel, dri-devel, linux-api,
linux-rockchip, dianders, marcheu, dbehr, olof, djkurtz, cf, xxm,
huangtao, kever.yang, yxj, xw, Mark Yao
In-Reply-To: <1417511600-7054-1-git-send-email-mark.yao@rock-chips.com>
This add a display subsystem comprise the all display interface nodes.
Signed-off-by: Mark Yao <mark.yao@rock-chips.com>
---
Changes in v2:
- add DRM master device node to list all display nodes that comprise
the graphics subsystem.
Changes in v3: None
Changes in v4: None
Changes in v5: None
Changes in v6: None
Changes in v7: None
Changes in v8: None
Changes in v9: None
Changes in v10: None
Changes in v11: None
Changes in v12: None
Changes in v13: None
Changes in v14: None
Changes in v15: None
.../devicetree/bindings/video/rockchip-drm.txt | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
create mode 100644 Documentation/devicetree/bindings/video/rockchip-drm.txt
diff --git a/Documentation/devicetree/bindings/video/rockchip-drm.txt b/Documentation/devicetree/bindings/video/rockchip-drm.txt
new file mode 100644
index 0000000..7fff582
--- /dev/null
+++ b/Documentation/devicetree/bindings/video/rockchip-drm.txt
@@ -0,0 +1,19 @@
+Rockchip DRM master device
+================================
+
+The Rockchip DRM master device is a virtual device needed to list all
+vop devices or other display interface nodes that comprise the
+graphics subsystem.
+
+Required properties:
+- compatible: Should be "rockchip,display-subsystem"
+- ports: Should contain a list of phandles pointing to display interface port
+ of vop devices. vop definitions as defined in
+ Documentation/devicetree/bindings/video/rockchip-vop.txt
+
+example:
+
+display-subsystem {
+ compatible = "rockchip,display-subsystem";
+ ports = <&vopl_out>, <&vopb_out>;
+};
--
1.7.9.5
^ permalink raw reply related
* [PATCH v15 1/3] drm: rockchip: Add basic drm driver
From: Mark Yao @ 2014-12-02 9:15 UTC (permalink / raw)
To: heiko, Boris BREZILLON, David Airlie, Rob Clark, Daniel Vetter,
Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
Randy Dunlap, Grant Likely, Greg Kroah-Hartman, John Stultz,
Rom Lemarchand
Cc: devicetree, linux-doc, linux-kernel, dri-devel, linux-api,
linux-rockchip, dianders, marcheu, dbehr, olof, djkurtz, cf, xxm,
huangtao, kever.yang, yxj, xw, Mark Yao
In-Reply-To: <1417511600-7054-1-git-send-email-mark.yao@rock-chips.com>
This patch adds the basic structure of a DRM Driver for Rockchip Socs.
Signed-off-by: Mark Yao <mark.yao@rock-chips.com>
Signed-off-by: Daniel Kurtz <djkurtz@chromium.org>
Acked-by: Daniel Vetter <daniel@ffwll.ch>
Reviewed-by: Rob Clark <robdclark@gmail.com>
---
Changes in v2:
- use the component framework to defer main drm driver probe
until all VOP devices have been probed.
- use dma-mapping API with ARM_DMA_USE_IOMMU, create dma mapping by
master device and each vop device can shared the drm dma mapping.
- use drm_crtc_init_with_planes and drm_universal_plane_init.
- remove unnecessary middle layers.
- add cursor set, move funcs to rockchip drm crtc.
- use vop reset at first init
- reference framebuffer when used and unreference when swap out vop
Changes in v3:
- change "crtc->fb" to "crtc->primary-fb"
Adviced by Daniel Vetter
- init cursor plane with universal api, remove unnecessary cursor set,move
Changes in v4:
Adviced by David Herrmann
- remove drm_platform_*() usage, use register drm device directly.
Adviced by Rob Clark
- remove special mmap ioctl, do userspace mmap with normal mmap() or mmap offset
Changes in v5:
Adviced by Arnd Bergmann
- doing DMA start with a 32-bit masks with dma_mask and dma_coherent_mark
- fix some incorrect dependencies.
Adviced by Boris BREZILLON
- fix some mistake and bugs.
Adviced by Daniel Vetter
- drop all special ioctl and use generic kms ioctl instead.
Adviced by Rob Clark
- use unlocked api for drm_fb_helper_restore_fbdev_mode.
- remove unused rockchip_gem_prime_import_sg_table.
Changes in v6:
- set gem buffer pitch 64 bytes align, needed by mali gpu.
Adviced by Daniel Kurtz
- fix some mistake, bugs, remove unused define, more better code style etc.
- use clk_prepare()/unprepare() at probe()/remove() and clk_enable()/disable()
at runtime instead of clk_prepare_enable().
- provide a help function from vop for encoder to do mode config, instead of
using drm_diaplay_mode private method.
- change vop mode_set timing to make it more safely.
Changes in v7:
- fix memory leakage problem
Changes in v8:
- fix iommu crash when use dual crtc.
- use frame start interrupt for vsync instead of line flag interrupt,
because the win config take affect at frame start time, if we use ling flag
interrupt, the address check often failed.
Adviced by Daniel Kurtz
- fix some bugs, mistake, remove unused function
- keep clock and vop disabled when probe end
- use drm_plane_helper_check_update to check update_plane if vaild
Changes in v9:
- fix suspend and resume bug, make iommu attach and detach safely.
Changes in v10:
Adviced by Andrzej Hajda
- check drm_dev if it's NULL at PM suspend/resume
Adviced by Sean Paul
- use drm_fb_helper_prepare to init fb_helper funcs
- Optimized code structure and remove some unnecessary variables.
Changes in v11:
- fix mistake that use wrong variable at rockchip sys_resume/sys_suspend.
Changes in v12:
- fix compile problem with drm-next
Adviced by Daniel Kurtz
- Optimization framebuffer reference/unreference
- Optimization Code structure
- fix pm suspend/resume problem
- fix vblank irq can't close problem
Changes in v13:
- fix vop compile warning.
Adviced by Daniel Vetter
- directly call rockchip_drm_load before register instead of
call ->load at the middle of drm register.
Changes in v14:
- revert v13 directly call rockchip_drm_load at bind, connector,
crtc _init should before dev-node kms object lookup idr and
conector sysfs need below minor node register, I don't like
split the connector init and register, so just call ->load at
the midile of drm register.
Changes in v15:
- remove depends on ARM_DMA_USE_IOMMU & IOMMU_API which cause
recursive dependency problem
- fix compile problems when build as a module.
drivers/gpu/drm/Kconfig | 2 +
drivers/gpu/drm/Makefile | 1 +
drivers/gpu/drm/rockchip/Kconfig | 17 +
drivers/gpu/drm/rockchip/Makefile | 8 +
drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 551 ++++++++++
drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 68 ++
drivers/gpu/drm/rockchip/rockchip_drm_fb.c | 201 ++++
drivers/gpu/drm/rockchip/rockchip_drm_fb.h | 28 +
drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c | 210 ++++
drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h | 21 +
drivers/gpu/drm/rockchip/rockchip_drm_gem.c | 294 +++++
drivers/gpu/drm/rockchip/rockchip_drm_gem.h | 54 +
drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 1455 +++++++++++++++++++++++++
drivers/gpu/drm/rockchip/rockchip_drm_vop.h | 201 ++++
14 files changed, 3111 insertions(+)
create mode 100644 drivers/gpu/drm/rockchip/Kconfig
create mode 100644 drivers/gpu/drm/rockchip/Makefile
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_drv.c
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_drv.h
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fb.c
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fb.h
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_gem.c
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_gem.h
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_vop.c
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_vop.h
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 24c2d7c..c3413b6 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -167,6 +167,8 @@ config DRM_SAVAGE
source "drivers/gpu/drm/exynos/Kconfig"
+source "drivers/gpu/drm/rockchip/Kconfig"
+
source "drivers/gpu/drm/vmwgfx/Kconfig"
source "drivers/gpu/drm/gma500/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 47d8986..66e4039 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -49,6 +49,7 @@ obj-$(CONFIG_DRM_VMWGFX)+= vmwgfx/
obj-$(CONFIG_DRM_VIA) +=via/
obj-$(CONFIG_DRM_NOUVEAU) +=nouveau/
obj-$(CONFIG_DRM_EXYNOS) +=exynos/
+obj-$(CONFIG_DRM_ROCKCHIP) +=rockchip/
obj-$(CONFIG_DRM_GMA500) += gma500/
obj-$(CONFIG_DRM_UDL) += udl/
obj-$(CONFIG_DRM_AST) += ast/
diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
new file mode 100644
index 0000000..ca9f085
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/Kconfig
@@ -0,0 +1,17 @@
+config DRM_ROCKCHIP
+ tristate "DRM Support for Rockchip"
+ depends on DRM && ROCKCHIP_IOMMU
+ select DRM_KMS_HELPER
+ select DRM_KMS_FB_HELPER
+ select DRM_PANEL
+ select FB_CFB_FILLRECT
+ select FB_CFB_COPYAREA
+ select FB_CFB_IMAGEBLIT
+ select VT_HW_CONSOLE_BINDING if FRAMEBUFFER_CONSOLE
+ select VIDEOMODE_HELPERS
+ help
+ Choose this option if you have a Rockchip soc chipset.
+ This driver provides kernel mode setting and buffer
+ management to userspace. This driver does not provide
+ 2D or 3D acceleration; acceleration is performed by other
+ IP found on the SoC.
diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile
new file mode 100644
index 0000000..2cb0672
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for the drm device driver. This driver provides support for the
+# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
+
+rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o rockchip_drm_fbdev.o \
+ rockchip_drm_gem.o
+
+obj-$(CONFIG_DRM_ROCKCHIP) += rockchipdrm.o rockchip_drm_vop.o
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
new file mode 100644
index 0000000..a798c7c
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
@@ -0,0 +1,551 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao@rock-chips.com>
+ *
+ * based on exynos_drm_drv.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 <asm/dma-iommu.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <linux/dma-mapping.h>
+#include <linux/pm_runtime.h>
+#include <linux/of_graph.h>
+#include <linux/component.h>
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_fb.h"
+#include "rockchip_drm_fbdev.h"
+#include "rockchip_drm_gem.h"
+
+#define DRIVER_NAME "rockchip"
+#define DRIVER_DESC "RockChip Soc DRM"
+#define DRIVER_DATE "20140818"
+#define DRIVER_MAJOR 1
+#define DRIVER_MINOR 0
+
+/*
+ * Attach a (component) device to the shared drm dma mapping from master drm
+ * device. This is used by the VOPs to map GEM buffers to a common DMA
+ * mapping.
+ */
+int rockchip_drm_dma_attach_device(struct drm_device *drm_dev,
+ struct device *dev)
+{
+ struct dma_iommu_mapping *mapping = drm_dev->dev->archdata.mapping;
+ int ret;
+
+ ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+
+ dma_set_max_seg_size(dev, DMA_BIT_MASK(32));
+
+ return arm_iommu_attach_device(dev, mapping);
+}
+EXPORT_SYMBOL_GPL(rockchip_drm_dma_attach_device);
+
+void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
+ struct device *dev)
+{
+ arm_iommu_detach_device(dev);
+}
+EXPORT_SYMBOL_GPL(rockchip_drm_dma_detach_device);
+
+int rockchip_register_crtc_funcs(struct drm_device *dev,
+ const struct rockchip_crtc_funcs *crtc_funcs,
+ int pipe)
+{
+ struct rockchip_drm_private *priv = dev->dev_private;
+
+ if (pipe > ROCKCHIP_MAX_CRTC)
+ return -EINVAL;
+
+ priv->crtc_funcs[pipe] = crtc_funcs;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(rockchip_register_crtc_funcs);
+
+void rockchip_unregister_crtc_funcs(struct drm_device *dev, int pipe)
+{
+ struct rockchip_drm_private *priv = dev->dev_private;
+
+ if (pipe > ROCKCHIP_MAX_CRTC)
+ return;
+
+ priv->crtc_funcs[pipe] = NULL;
+}
+EXPORT_SYMBOL_GPL(rockchip_unregister_crtc_funcs);
+
+static struct drm_crtc *rockchip_crtc_from_pipe(struct drm_device *drm,
+ int pipe)
+{
+ struct drm_crtc *crtc;
+ int i = 0;
+
+ list_for_each_entry(crtc, &drm->mode_config.crtc_list, head)
+ if (i++ == pipe)
+ return crtc;
+
+ return NULL;
+}
+
+static int rockchip_drm_crtc_enable_vblank(struct drm_device *dev, int pipe)
+{
+ struct rockchip_drm_private *priv = dev->dev_private;
+ struct drm_crtc *crtc = rockchip_crtc_from_pipe(dev, pipe);
+
+ if (crtc && priv->crtc_funcs[pipe] &&
+ priv->crtc_funcs[pipe]->enable_vblank)
+ return priv->crtc_funcs[pipe]->enable_vblank(crtc);
+
+ return 0;
+}
+
+static void rockchip_drm_crtc_disable_vblank(struct drm_device *dev, int pipe)
+{
+ struct rockchip_drm_private *priv = dev->dev_private;
+ struct drm_crtc *crtc = rockchip_crtc_from_pipe(dev, pipe);
+
+ if (crtc && priv->crtc_funcs[pipe] &&
+ priv->crtc_funcs[pipe]->enable_vblank)
+ priv->crtc_funcs[pipe]->disable_vblank(crtc);
+}
+
+static int rockchip_drm_load(struct drm_device *drm_dev, unsigned long flags)
+{
+ struct rockchip_drm_private *private;
+ struct dma_iommu_mapping *mapping;
+ struct device *dev = drm_dev->dev;
+ int ret;
+
+ private = devm_kzalloc(drm_dev->dev, sizeof(*private), GFP_KERNEL);
+ if (!private)
+ return -ENOMEM;
+
+ drm_dev->dev_private = private;
+
+ drm_mode_config_init(drm_dev);
+
+ rockchip_drm_mode_config_init(drm_dev);
+
+ dev->dma_parms = devm_kzalloc(dev, sizeof(*dev->dma_parms),
+ GFP_KERNEL);
+ if (!dev->dma_parms) {
+ ret = -ENOMEM;
+ goto err_config_cleanup;
+ }
+
+ /* TODO(djkurtz): fetch the mapping start/size from somewhere */
+ mapping = arm_iommu_create_mapping(&platform_bus_type, 0x00000000,
+ SZ_2G);
+ if (IS_ERR(mapping)) {
+ ret = PTR_ERR(mapping);
+ goto err_config_cleanup;
+ }
+
+ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
+ if (ret)
+ goto err_release_mapping;
+
+ dma_set_max_seg_size(dev, DMA_BIT_MASK(32));
+
+ ret = arm_iommu_attach_device(dev, mapping);
+ if (ret)
+ goto err_release_mapping;
+
+ /* Try to bind all sub drivers. */
+ ret = component_bind_all(dev, drm_dev);
+ if (ret)
+ goto err_detach_device;
+
+ /* init kms poll for handling hpd */
+ drm_kms_helper_poll_init(drm_dev);
+
+ /*
+ * enable drm irq mode.
+ * - with irq_enabled = true, we can use the vblank feature.
+ */
+ drm_dev->irq_enabled = true;
+
+ ret = drm_vblank_init(drm_dev, ROCKCHIP_MAX_CRTC);
+ if (ret)
+ goto err_kms_helper_poll_fini;
+
+ /*
+ * with vblank_disable_allowed = true, vblank interrupt will be disabled
+ * by drm timer once a current process gives up ownership of
+ * vblank event.(after drm_vblank_put function is called)
+ */
+ drm_dev->vblank_disable_allowed = true;
+
+ ret = rockchip_drm_fbdev_init(drm_dev);
+ if (ret)
+ goto err_vblank_cleanup;
+
+ return 0;
+err_vblank_cleanup:
+ drm_vblank_cleanup(drm_dev);
+err_kms_helper_poll_fini:
+ drm_kms_helper_poll_fini(drm_dev);
+ component_unbind_all(dev, drm_dev);
+err_detach_device:
+ arm_iommu_detach_device(dev);
+err_release_mapping:
+ arm_iommu_release_mapping(dev->archdata.mapping);
+err_config_cleanup:
+ drm_mode_config_cleanup(drm_dev);
+ drm_dev->dev_private = NULL;
+ return ret;
+}
+
+static int rockchip_drm_unload(struct drm_device *drm_dev)
+{
+ struct device *dev = drm_dev->dev;
+
+ rockchip_drm_fbdev_fini(drm_dev);
+ drm_vblank_cleanup(drm_dev);
+ drm_kms_helper_poll_fini(drm_dev);
+ component_unbind_all(dev, drm_dev);
+ arm_iommu_detach_device(dev);
+ arm_iommu_release_mapping(dev->archdata.mapping);
+ drm_mode_config_cleanup(drm_dev);
+ drm_dev->dev_private = NULL;
+
+ return 0;
+}
+
+void rockchip_drm_lastclose(struct drm_device *dev)
+{
+ struct rockchip_drm_private *priv = dev->dev_private;
+
+ drm_fb_helper_restore_fbdev_mode_unlocked(&priv->fbdev_helper);
+}
+
+static const struct file_operations rockchip_drm_driver_fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .mmap = rockchip_gem_mmap,
+ .poll = drm_poll,
+ .read = drm_read,
+ .unlocked_ioctl = drm_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = drm_compat_ioctl,
+#endif
+ .release = drm_release,
+};
+
+const struct vm_operations_struct rockchip_drm_vm_ops = {
+ .open = drm_gem_vm_open,
+ .close = drm_gem_vm_close,
+};
+
+static struct drm_driver rockchip_drm_driver = {
+ .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME,
+ .load = rockchip_drm_load,
+ .unload = rockchip_drm_unload,
+ .lastclose = rockchip_drm_lastclose,
+ .get_vblank_counter = drm_vblank_count,
+ .enable_vblank = rockchip_drm_crtc_enable_vblank,
+ .disable_vblank = rockchip_drm_crtc_disable_vblank,
+ .gem_vm_ops = &rockchip_drm_vm_ops,
+ .gem_free_object = rockchip_gem_free_object,
+ .dumb_create = rockchip_gem_dumb_create,
+ .dumb_map_offset = rockchip_gem_dumb_map_offset,
+ .dumb_destroy = drm_gem_dumb_destroy,
+ .prime_handle_to_fd = drm_gem_prime_handle_to_fd,
+ .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
+ .gem_prime_import = drm_gem_prime_import,
+ .gem_prime_export = drm_gem_prime_export,
+ .gem_prime_get_sg_table = rockchip_gem_prime_get_sg_table,
+ .gem_prime_vmap = rockchip_gem_prime_vmap,
+ .gem_prime_vunmap = rockchip_gem_prime_vunmap,
+ .gem_prime_mmap = rockchip_gem_mmap_buf,
+ .fops = &rockchip_drm_driver_fops,
+ .name = DRIVER_NAME,
+ .desc = DRIVER_DESC,
+ .date = DRIVER_DATE,
+ .major = DRIVER_MAJOR,
+ .minor = DRIVER_MINOR,
+};
+
+#ifdef CONFIG_PM_SLEEP
+static int rockchip_drm_sys_suspend(struct device *dev)
+{
+ struct drm_device *drm = dev_get_drvdata(dev);
+ struct drm_connector *connector;
+
+ if (!drm)
+ return 0;
+
+ drm_modeset_lock_all(drm);
+ list_for_each_entry(connector, &drm->mode_config.connector_list, head) {
+ int old_dpms = connector->dpms;
+
+ if (connector->funcs->dpms)
+ connector->funcs->dpms(connector, DRM_MODE_DPMS_OFF);
+
+ /* Set the old mode back to the connector for resume */
+ connector->dpms = old_dpms;
+ }
+ drm_modeset_unlock_all(drm);
+
+ return 0;
+}
+
+static int rockchip_drm_sys_resume(struct device *dev)
+{
+ struct drm_device *drm = dev_get_drvdata(dev);
+ struct drm_connector *connector;
+ enum drm_connector_status status;
+ bool changed = false;
+
+ if (!drm)
+ return 0;
+
+ drm_modeset_lock_all(drm);
+ list_for_each_entry(connector, &drm->mode_config.connector_list, head) {
+ int desired_mode = connector->dpms;
+
+ /*
+ * at suspend time, we save dpms to connector->dpms,
+ * restore the old_dpms, and at current time, the connector
+ * dpms status must be DRM_MODE_DPMS_OFF.
+ */
+ connector->dpms = DRM_MODE_DPMS_OFF;
+
+ /*
+ * If the connector has been disconnected during suspend,
+ * disconnect it from the encoder and leave it off. We'll notify
+ * userspace at the end.
+ */
+ if (desired_mode == DRM_MODE_DPMS_ON) {
+ status = connector->funcs->detect(connector, true);
+ if (status == connector_status_disconnected) {
+ connector->encoder = NULL;
+ connector->status = status;
+ changed = true;
+ continue;
+ }
+ }
+ if (connector->funcs->dpms)
+ connector->funcs->dpms(connector, desired_mode);
+ }
+ drm_modeset_unlock_all(drm);
+
+ drm_helper_resume_force_mode(drm);
+
+ if (changed)
+ drm_kms_helper_hotplug_event(drm);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops rockchip_drm_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(rockchip_drm_sys_suspend,
+ rockchip_drm_sys_resume)
+};
+
+/*
+ * @node: device tree node containing encoder input ports
+ * @encoder: drm_encoder
+ */
+int rockchip_drm_encoder_get_mux_id(struct device_node *node,
+ struct drm_encoder *encoder)
+{
+ struct device_node *ep = NULL;
+ struct drm_crtc *crtc = encoder->crtc;
+ struct of_endpoint endpoint;
+ struct device_node *port;
+ int ret;
+
+ if (!node || !crtc)
+ return -EINVAL;
+
+ do {
+ ep = of_graph_get_next_endpoint(node, ep);
+ if (!ep)
+ break;
+
+ port = of_graph_get_remote_port(ep);
+ of_node_put(port);
+ if (port == crtc->port) {
+ ret = of_graph_parse_endpoint(ep, &endpoint);
+ return ret ?: endpoint.id;
+ }
+ } while (ep);
+
+ return -EINVAL;
+}
+
+static int compare_of(struct device *dev, void *data)
+{
+ struct device_node *np = data;
+
+ return dev->of_node == np;
+}
+
+static void rockchip_add_endpoints(struct device *dev,
+ struct component_match **match,
+ struct device_node *port)
+{
+ struct device_node *ep, *remote;
+
+ for_each_child_of_node(port, ep) {
+ remote = of_graph_get_remote_port_parent(ep);
+ if (!remote || !of_device_is_available(remote)) {
+ of_node_put(remote);
+ continue;
+ } else if (!of_device_is_available(remote->parent)) {
+ dev_warn(dev, "parent device of %s is not available\n",
+ remote->full_name);
+ of_node_put(remote);
+ continue;
+ }
+
+ component_match_add(dev, match, compare_of, remote);
+ of_node_put(remote);
+ }
+}
+
+static int rockchip_drm_bind(struct device *dev)
+{
+ struct drm_device *drm;
+ int ret;
+
+ drm = drm_dev_alloc(&rockchip_drm_driver, dev);
+ if (!drm)
+ return -ENOMEM;
+
+ ret = drm_dev_set_unique(drm, "%s", dev_name(dev));
+ if (ret)
+ goto err_free;
+
+ ret = drm_dev_register(drm, 0);
+ if (ret)
+ goto err_free;
+
+ dev_set_drvdata(dev, drm);
+
+ return 0;
+
+err_free:
+ drm_dev_unref(drm);
+ return ret;
+}
+
+static void rockchip_drm_unbind(struct device *dev)
+{
+ struct drm_device *drm = dev_get_drvdata(dev);
+
+ drm_dev_unregister(drm);
+ drm_dev_unref(drm);
+ dev_set_drvdata(dev, NULL);
+}
+
+static const struct component_master_ops rockchip_drm_ops = {
+ .bind = rockchip_drm_bind,
+ .unbind = rockchip_drm_unbind,
+};
+
+static int rockchip_drm_platform_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct component_match *match = NULL;
+ struct device_node *np = dev->of_node;
+ struct device_node *port;
+ int i;
+
+ if (!np)
+ return -ENODEV;
+ /*
+ * Bind the crtc ports first, so that
+ * drm_of_find_possible_crtcs called from encoder .bind callbacks
+ * works as expected.
+ */
+ for (i = 0;; i++) {
+ port = of_parse_phandle(np, "ports", i);
+ if (!port)
+ break;
+
+ if (!of_device_is_available(port->parent)) {
+ of_node_put(port);
+ continue;
+ }
+
+ component_match_add(dev, &match, compare_of, port->parent);
+ of_node_put(port);
+ }
+
+ if (i == 0) {
+ dev_err(dev, "missing 'ports' property\n");
+ return -ENODEV;
+ }
+
+ if (!match) {
+ dev_err(dev, "No available vop found for display-subsystem.\n");
+ return -ENODEV;
+ }
+ /*
+ * For each bound crtc, bind the encoders attached to its
+ * remote endpoint.
+ */
+ for (i = 0;; i++) {
+ port = of_parse_phandle(np, "ports", i);
+ if (!port)
+ break;
+
+ if (!of_device_is_available(port->parent)) {
+ of_node_put(port);
+ continue;
+ }
+
+ rockchip_add_endpoints(dev, &match, port);
+ of_node_put(port);
+ }
+
+ return component_master_add_with_match(dev, &rockchip_drm_ops, match);
+}
+
+static int rockchip_drm_platform_remove(struct platform_device *pdev)
+{
+ component_master_del(&pdev->dev, &rockchip_drm_ops);
+
+ return 0;
+}
+
+static const struct of_device_id rockchip_drm_dt_ids[] = {
+ { .compatible = "rockchip,display-subsystem", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, rockchip_drm_dt_ids);
+
+static struct platform_driver rockchip_drm_platform_driver = {
+ .probe = rockchip_drm_platform_probe,
+ .remove = rockchip_drm_platform_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "rockchip-drm",
+ .of_match_table = rockchip_drm_dt_ids,
+ .pm = &rockchip_drm_pm_ops,
+ },
+};
+
+module_platform_driver(rockchip_drm_platform_driver);
+
+MODULE_AUTHOR("Mark Yao <mark.yao@rock-chips.com>");
+MODULE_DESCRIPTION("ROCKCHIP DRM Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
new file mode 100644
index 0000000..dc4e5f0
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao@rock-chips.com>
+ *
+ * based on exynos_drm_drv.h
+ *
+ * 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 _ROCKCHIP_DRM_DRV_H
+#define _ROCKCHIP_DRM_DRV_H
+
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem.h>
+
+#include <linux/module.h>
+#include <linux/component.h>
+
+#define ROCKCHIP_MAX_FB_BUFFER 3
+#define ROCKCHIP_MAX_CONNECTOR 2
+#define ROCKCHIP_MAX_CRTC 2
+
+struct drm_device;
+struct drm_connector;
+
+/*
+ * Rockchip drm private crtc funcs.
+ * @enable_vblank: enable crtc vblank irq.
+ * @disable_vblank: disable crtc vblank irq.
+ */
+struct rockchip_crtc_funcs {
+ int (*enable_vblank)(struct drm_crtc *crtc);
+ void (*disable_vblank)(struct drm_crtc *crtc);
+};
+
+/*
+ * Rockchip drm private structure.
+ *
+ * @crtc: array of enabled CRTCs, used to map from "pipe" to drm_crtc.
+ * @num_pipe: number of pipes for this device.
+ */
+struct rockchip_drm_private {
+ struct drm_fb_helper fbdev_helper;
+ struct drm_gem_object *fbdev_bo;
+ const struct rockchip_crtc_funcs *crtc_funcs[ROCKCHIP_MAX_CRTC];
+};
+
+int rockchip_register_crtc_funcs(struct drm_device *dev,
+ const struct rockchip_crtc_funcs *crtc_funcs,
+ int pipe);
+void rockchip_unregister_crtc_funcs(struct drm_device *dev, int pipe);
+int rockchip_drm_encoder_get_mux_id(struct device_node *node,
+ struct drm_encoder *encoder);
+int rockchip_drm_crtc_mode_config(struct drm_crtc *crtc, int connector_type,
+ int out_mode);
+int rockchip_drm_dma_attach_device(struct drm_device *drm_dev,
+ struct device *dev);
+void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
+ struct device *dev);
+
+#endif /* _ROCKCHIP_DRM_DRV_H_ */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
new file mode 100644
index 0000000..77d5289
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao@rock-chips.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <drm/drm.h>
+#include <drm/drmP.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_crtc_helper.h>
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_gem.h"
+
+#define to_rockchip_fb(x) container_of(x, struct rockchip_drm_fb, fb)
+
+struct rockchip_drm_fb {
+ struct drm_framebuffer fb;
+ struct drm_gem_object *obj[ROCKCHIP_MAX_FB_BUFFER];
+};
+
+struct drm_gem_object *rockchip_fb_get_gem_obj(struct drm_framebuffer *fb,
+ unsigned int plane)
+{
+ struct rockchip_drm_fb *rk_fb = to_rockchip_fb(fb);
+
+ if (plane >= ROCKCHIP_MAX_FB_BUFFER)
+ return NULL;
+
+ return rk_fb->obj[plane];
+}
+EXPORT_SYMBOL_GPL(rockchip_fb_get_gem_obj);
+
+static void rockchip_drm_fb_destroy(struct drm_framebuffer *fb)
+{
+ struct rockchip_drm_fb *rockchip_fb = to_rockchip_fb(fb);
+ struct drm_gem_object *obj;
+ int i;
+
+ for (i = 0; i < ROCKCHIP_MAX_FB_BUFFER; i++) {
+ obj = rockchip_fb->obj[i];
+ if (obj)
+ drm_gem_object_unreference_unlocked(obj);
+ }
+
+ drm_framebuffer_cleanup(fb);
+ kfree(rockchip_fb);
+}
+
+static int rockchip_drm_fb_create_handle(struct drm_framebuffer *fb,
+ struct drm_file *file_priv,
+ unsigned int *handle)
+{
+ struct rockchip_drm_fb *rockchip_fb = to_rockchip_fb(fb);
+
+ return drm_gem_handle_create(file_priv,
+ rockchip_fb->obj[0], handle);
+}
+
+static struct drm_framebuffer_funcs rockchip_drm_fb_funcs = {
+ .destroy = rockchip_drm_fb_destroy,
+ .create_handle = rockchip_drm_fb_create_handle,
+};
+
+static struct rockchip_drm_fb *
+rockchip_fb_alloc(struct drm_device *dev, struct drm_mode_fb_cmd2 *mode_cmd,
+ struct drm_gem_object **obj, unsigned int num_planes)
+{
+ struct rockchip_drm_fb *rockchip_fb;
+ int ret;
+ int i;
+
+ rockchip_fb = kzalloc(sizeof(*rockchip_fb), GFP_KERNEL);
+ if (!rockchip_fb)
+ return ERR_PTR(-ENOMEM);
+
+ drm_helper_mode_fill_fb_struct(&rockchip_fb->fb, mode_cmd);
+
+ for (i = 0; i < num_planes; i++)
+ rockchip_fb->obj[i] = obj[i];
+
+ ret = drm_framebuffer_init(dev, &rockchip_fb->fb,
+ &rockchip_drm_fb_funcs);
+ if (ret) {
+ dev_err(dev->dev, "Failed to initialize framebuffer: %d\n",
+ ret);
+ kfree(rockchip_fb);
+ return ERR_PTR(ret);
+ }
+
+ return rockchip_fb;
+}
+
+static struct drm_framebuffer *
+rockchip_user_fb_create(struct drm_device *dev, struct drm_file *file_priv,
+ struct drm_mode_fb_cmd2 *mode_cmd)
+{
+ struct rockchip_drm_fb *rockchip_fb;
+ struct drm_gem_object *objs[ROCKCHIP_MAX_FB_BUFFER];
+ struct drm_gem_object *obj;
+ unsigned int hsub;
+ unsigned int vsub;
+ int num_planes;
+ int ret;
+ int i;
+
+ hsub = drm_format_horz_chroma_subsampling(mode_cmd->pixel_format);
+ vsub = drm_format_vert_chroma_subsampling(mode_cmd->pixel_format);
+ num_planes = min(drm_format_num_planes(mode_cmd->pixel_format),
+ ROCKCHIP_MAX_FB_BUFFER);
+
+ for (i = 0; i < num_planes; i++) {
+ unsigned int width = mode_cmd->width / (i ? hsub : 1);
+ unsigned int height = mode_cmd->height / (i ? vsub : 1);
+ unsigned int min_size;
+
+ obj = drm_gem_object_lookup(dev, file_priv,
+ mode_cmd->handles[i]);
+ if (!obj) {
+ dev_err(dev->dev, "Failed to lookup GEM object\n");
+ ret = -ENXIO;
+ goto err_gem_object_unreference;
+ }
+
+ min_size = (height - 1) * mode_cmd->pitches[i] +
+ mode_cmd->offsets[i] +
+ width * drm_format_plane_cpp(mode_cmd->pixel_format, i);
+
+ if (obj->size < min_size) {
+ drm_gem_object_unreference_unlocked(obj);
+ ret = -EINVAL;
+ goto err_gem_object_unreference;
+ }
+ objs[i] = obj;
+ }
+
+ rockchip_fb = rockchip_fb_alloc(dev, mode_cmd, objs, i);
+ if (IS_ERR(rockchip_fb)) {
+ ret = PTR_ERR(rockchip_fb);
+ goto err_gem_object_unreference;
+ }
+
+ return &rockchip_fb->fb;
+
+err_gem_object_unreference:
+ for (i--; i >= 0; i--)
+ drm_gem_object_unreference_unlocked(objs[i]);
+ return ERR_PTR(ret);
+}
+
+static void rockchip_drm_output_poll_changed(struct drm_device *dev)
+{
+ struct rockchip_drm_private *private = dev->dev_private;
+ struct drm_fb_helper *fb_helper = &private->fbdev_helper;
+
+ drm_fb_helper_hotplug_event(fb_helper);
+}
+
+static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = {
+ .fb_create = rockchip_user_fb_create,
+ .output_poll_changed = rockchip_drm_output_poll_changed,
+};
+
+struct drm_framebuffer *
+rockchip_drm_framebuffer_init(struct drm_device *dev,
+ struct drm_mode_fb_cmd2 *mode_cmd,
+ struct drm_gem_object *obj)
+{
+ struct rockchip_drm_fb *rockchip_fb;
+
+ rockchip_fb = rockchip_fb_alloc(dev, mode_cmd, &obj, 1);
+ if (IS_ERR(rockchip_fb))
+ return NULL;
+
+ return &rockchip_fb->fb;
+}
+
+void rockchip_drm_mode_config_init(struct drm_device *dev)
+{
+ dev->mode_config.min_width = 0;
+ dev->mode_config.min_height = 0;
+
+ /*
+ * set max width and height as default value(4096x4096).
+ * this value would be used to check framebuffer size limitation
+ * at drm_mode_addfb().
+ */
+ dev->mode_config.max_width = 4096;
+ dev->mode_config.max_height = 4096;
+
+ dev->mode_config.funcs = &rockchip_drm_mode_config_funcs;
+}
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.h b/drivers/gpu/drm/rockchip/rockchip_drm_fb.h
new file mode 100644
index 0000000..09574d4
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao@rock-chips.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _ROCKCHIP_DRM_FB_H
+#define _ROCKCHIP_DRM_FB_H
+
+struct drm_framebuffer *
+rockchip_drm_framebuffer_init(struct drm_device *dev,
+ struct drm_mode_fb_cmd2 *mode_cmd,
+ struct drm_gem_object *obj);
+void rockchip_drm_framebuffer_fini(struct drm_framebuffer *fb);
+
+void rockchip_drm_mode_config_init(struct drm_device *dev);
+
+struct drm_gem_object *rockchip_fb_get_gem_obj(struct drm_framebuffer *fb,
+ unsigned int plane);
+#endif /* _ROCKCHIP_DRM_FB_H */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c
new file mode 100644
index 0000000..a5d889a
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao@rock-chips.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <drm/drm.h>
+#include <drm/drmP.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_crtc_helper.h>
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_gem.h"
+#include "rockchip_drm_fb.h"
+
+#define PREFERRED_BPP 32
+#define to_drm_private(x) \
+ container_of(x, struct rockchip_drm_private, fbdev_helper)
+
+static int rockchip_fbdev_mmap(struct fb_info *info,
+ struct vm_area_struct *vma)
+{
+ struct drm_fb_helper *helper = info->par;
+ struct rockchip_drm_private *private = to_drm_private(helper);
+
+ return rockchip_gem_mmap_buf(private->fbdev_bo, vma);
+}
+
+static struct fb_ops rockchip_drm_fbdev_ops = {
+ .owner = THIS_MODULE,
+ .fb_mmap = rockchip_fbdev_mmap,
+ .fb_fillrect = cfb_fillrect,
+ .fb_copyarea = cfb_copyarea,
+ .fb_imageblit = cfb_imageblit,
+ .fb_check_var = drm_fb_helper_check_var,
+ .fb_set_par = drm_fb_helper_set_par,
+ .fb_blank = drm_fb_helper_blank,
+ .fb_pan_display = drm_fb_helper_pan_display,
+ .fb_setcmap = drm_fb_helper_setcmap,
+};
+
+static int rockchip_drm_fbdev_create(struct drm_fb_helper *helper,
+ struct drm_fb_helper_surface_size *sizes)
+{
+ struct rockchip_drm_private *private = to_drm_private(helper);
+ struct drm_mode_fb_cmd2 mode_cmd = { 0 };
+ struct drm_device *dev = helper->dev;
+ struct rockchip_gem_object *rk_obj;
+ struct drm_framebuffer *fb;
+ unsigned int bytes_per_pixel;
+ unsigned long offset;
+ struct fb_info *fbi;
+ size_t size;
+ int ret;
+
+ bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8);
+
+ mode_cmd.width = sizes->surface_width;
+ mode_cmd.height = sizes->surface_height;
+ mode_cmd.pitches[0] = sizes->surface_width * bytes_per_pixel;
+ mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
+ sizes->surface_depth);
+
+ size = mode_cmd.pitches[0] * mode_cmd.height;
+
+ rk_obj = rockchip_gem_create_object(dev, size);
+ if (IS_ERR(rk_obj))
+ return -ENOMEM;
+
+ private->fbdev_bo = &rk_obj->base;
+
+ fbi = framebuffer_alloc(0, dev->dev);
+ if (!fbi) {
+ dev_err(dev->dev, "Failed to allocate framebuffer info.\n");
+ ret = -ENOMEM;
+ goto err_rockchip_gem_free_object;
+ }
+
+ helper->fb = rockchip_drm_framebuffer_init(dev, &mode_cmd,
+ private->fbdev_bo);
+ if (IS_ERR(helper->fb)) {
+ dev_err(dev->dev, "Failed to allocate DRM framebuffer.\n");
+ ret = PTR_ERR(helper->fb);
+ goto err_framebuffer_release;
+ }
+
+ helper->fbdev = fbi;
+
+ fbi->par = helper;
+ fbi->flags = FBINFO_FLAG_DEFAULT;
+ fbi->fbops = &rockchip_drm_fbdev_ops;
+
+ ret = fb_alloc_cmap(&fbi->cmap, 256, 0);
+ if (ret) {
+ dev_err(dev->dev, "Failed to allocate color map.\n");
+ goto err_drm_framebuffer_unref;
+ }
+
+ fb = helper->fb;
+ drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth);
+ drm_fb_helper_fill_var(fbi, helper, fb->width, fb->height);
+
+ offset = fbi->var.xoffset * bytes_per_pixel;
+ offset += fbi->var.yoffset * fb->pitches[0];
+
+ dev->mode_config.fb_base = 0;
+ fbi->screen_base = rk_obj->kvaddr + offset;
+ fbi->screen_size = rk_obj->base.size;
+ fbi->fix.smem_len = rk_obj->base.size;
+
+ DRM_DEBUG_KMS("FB [%dx%d]-%d kvaddr=%p offset=%ld size=%d\n",
+ fb->width, fb->height, fb->depth, rk_obj->kvaddr,
+ offset, size);
+ return 0;
+
+err_drm_framebuffer_unref:
+ drm_framebuffer_unreference(helper->fb);
+err_framebuffer_release:
+ framebuffer_release(fbi);
+err_rockchip_gem_free_object:
+ rockchip_gem_free_object(&rk_obj->base);
+ return ret;
+}
+
+static const struct drm_fb_helper_funcs rockchip_drm_fb_helper_funcs = {
+ .fb_probe = rockchip_drm_fbdev_create,
+};
+
+int rockchip_drm_fbdev_init(struct drm_device *dev)
+{
+ struct rockchip_drm_private *private = dev->dev_private;
+ struct drm_fb_helper *helper;
+ unsigned int num_crtc;
+ int ret;
+
+ if (!dev->mode_config.num_crtc || !dev->mode_config.num_connector)
+ return -EINVAL;
+
+ num_crtc = dev->mode_config.num_crtc;
+
+ helper = &private->fbdev_helper;
+
+ drm_fb_helper_prepare(dev, helper, &rockchip_drm_fb_helper_funcs);
+
+ ret = drm_fb_helper_init(dev, helper, num_crtc, ROCKCHIP_MAX_CONNECTOR);
+ if (ret < 0) {
+ dev_err(dev->dev, "Failed to initialize drm fb helper - %d.\n",
+ ret);
+ return ret;
+ }
+
+ ret = drm_fb_helper_single_add_all_connectors(helper);
+ if (ret < 0) {
+ dev_err(dev->dev, "Failed to add connectors - %d.\n", ret);
+ goto err_drm_fb_helper_fini;
+ }
+
+ /* disable all the possible outputs/crtcs before entering KMS mode */
+ drm_helper_disable_unused_functions(dev);
+
+ ret = drm_fb_helper_initial_config(helper, PREFERRED_BPP);
+ if (ret < 0) {
+ dev_err(dev->dev, "Failed to set initial hw config - %d.\n",
+ ret);
+ goto err_drm_fb_helper_fini;
+ }
+
+ return 0;
+
+err_drm_fb_helper_fini:
+ drm_fb_helper_fini(helper);
+ return ret;
+}
+
+void rockchip_drm_fbdev_fini(struct drm_device *dev)
+{
+ struct rockchip_drm_private *private = dev->dev_private;
+ struct drm_fb_helper *helper;
+
+ helper = &private->fbdev_helper;
+
+ if (helper->fbdev) {
+ struct fb_info *info;
+ int ret;
+
+ info = helper->fbdev;
+ ret = unregister_framebuffer(info);
+ if (ret < 0)
+ DRM_DEBUG_KMS("failed unregister_framebuffer() - %d\n",
+ ret);
+
+ if (info->cmap.len)
+ fb_dealloc_cmap(&info->cmap);
+
+ framebuffer_release(info);
+ }
+
+ if (helper->fb)
+ drm_framebuffer_unreference(helper->fb);
+
+ drm_fb_helper_fini(helper);
+}
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h
new file mode 100644
index 0000000..50432e9
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao@rock-chips.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _ROCKCHIP_DRM_FBDEV_H
+#define _ROCKCHIP_DRM_FBDEV_H
+
+int rockchip_drm_fbdev_init(struct drm_device *dev);
+void rockchip_drm_fbdev_fini(struct drm_device *dev);
+
+#endif /* _ROCKCHIP_DRM_FBDEV_H */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_gem.c b/drivers/gpu/drm/rockchip/rockchip_drm_gem.c
new file mode 100644
index 0000000..bc98a22
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_gem.c
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao@rock-chips.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <drm/drm.h>
+#include <drm/drmP.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_vma_manager.h>
+
+#include <linux/dma-attrs.h>
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_gem.h"
+
+static int rockchip_gem_alloc_buf(struct rockchip_gem_object *rk_obj)
+{
+ struct drm_gem_object *obj = &rk_obj->base;
+ struct drm_device *drm = obj->dev;
+
+ init_dma_attrs(&rk_obj->dma_attrs);
+ dma_set_attr(DMA_ATTR_WRITE_COMBINE, &rk_obj->dma_attrs);
+
+ /* TODO(djkurtz): Use DMA_ATTR_NO_KERNEL_MAPPING except for fbdev */
+ rk_obj->kvaddr = dma_alloc_attrs(drm->dev, obj->size,
+ &rk_obj->dma_addr, GFP_KERNEL,
+ &rk_obj->dma_attrs);
+ if (IS_ERR(rk_obj->kvaddr)) {
+ int ret = PTR_ERR(rk_obj->kvaddr);
+
+ DRM_ERROR("failed to allocate %#x byte dma buffer, %d",
+ obj->size, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void rockchip_gem_free_buf(struct rockchip_gem_object *rk_obj)
+{
+ struct drm_gem_object *obj = &rk_obj->base;
+ struct drm_device *drm = obj->dev;
+
+ dma_free_attrs(drm->dev, obj->size, rk_obj->kvaddr, rk_obj->dma_addr,
+ &rk_obj->dma_attrs);
+}
+
+int rockchip_gem_mmap_buf(struct drm_gem_object *obj,
+ struct vm_area_struct *vma)
+{
+ struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
+ struct drm_device *drm = obj->dev;
+ unsigned long vm_size;
+
+ vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP;
+ vm_size = vma->vm_end - vma->vm_start;
+
+ if (vm_size > obj->size)
+ return -EINVAL;
+
+ return dma_mmap_attrs(drm->dev, vma, rk_obj->kvaddr, rk_obj->dma_addr,
+ obj->size, &rk_obj->dma_attrs);
+}
+
+/* drm driver mmap file operations */
+int rockchip_gem_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ struct drm_file *priv = filp->private_data;
+ struct drm_device *dev = priv->minor->dev;
+ struct drm_gem_object *obj;
+ struct drm_vma_offset_node *node;
+ int ret;
+
+ if (drm_device_is_unplugged(dev))
+ return -ENODEV;
+
+ mutex_lock(&dev->struct_mutex);
+
+ node = drm_vma_offset_exact_lookup(dev->vma_offset_manager,
+ vma->vm_pgoff,
+ vma_pages(vma));
+ if (!node) {
+ mutex_unlock(&dev->struct_mutex);
+ DRM_ERROR("failed to find vma node.\n");
+ return -EINVAL;
+ } else if (!drm_vma_node_is_allowed(node, filp)) {
+ mutex_unlock(&dev->struct_mutex);
+ return -EACCES;
+ }
+
+ obj = container_of(node, struct drm_gem_object, vma_node);
+ ret = rockchip_gem_mmap_buf(obj, vma);
+
+ mutex_unlock(&dev->struct_mutex);
+
+ return ret;
+}
+
+struct rockchip_gem_object *
+ rockchip_gem_create_object(struct drm_device *drm, unsigned int size)
+{
+ struct rockchip_gem_object *rk_obj;
+ struct drm_gem_object *obj;
+ int ret;
+
+ size = round_up(size, PAGE_SIZE);
+
+ rk_obj = kzalloc(sizeof(*rk_obj), GFP_KERNEL);
+ if (!rk_obj)
+ return ERR_PTR(-ENOMEM);
+
+ obj = &rk_obj->base;
+
+ drm_gem_private_object_init(drm, obj, size);
+
+ ret = rockchip_gem_alloc_buf(rk_obj);
+ if (ret)
+ goto err_free_rk_obj;
+
+ return rk_obj;
+
+err_free_rk_obj:
+ kfree(rk_obj);
+ return ERR_PTR(ret);
+}
+
+/*
+ * rockchip_gem_free_object - (struct drm_driver)->gem_free_object callback
+ * function
+ */
+void rockchip_gem_free_object(struct drm_gem_object *obj)
+{
+ struct rockchip_gem_object *rk_obj;
+
+ drm_gem_free_mmap_offset(obj);
+
+ rk_obj = to_rockchip_obj(obj);
+
+ rockchip_gem_free_buf(rk_obj);
+
+ kfree(rk_obj);
+}
+
+/*
+ * rockchip_gem_create_with_handle - allocate an object with the given
+ * size and create a gem handle on it
+ *
+ * returns a struct rockchip_gem_object* on success or ERR_PTR values
+ * on failure.
+ */
+static struct rockchip_gem_object *
+rockchip_gem_create_with_handle(struct drm_file *file_priv,
+ struct drm_device *drm, unsigned int size,
+ unsigned int *handle)
+{
+ struct rockchip_gem_object *rk_obj;
+ struct drm_gem_object *obj;
+ int ret;
+
+ rk_obj = rockchip_gem_create_object(drm, size);
+ if (IS_ERR(rk_obj))
+ return ERR_CAST(rk_obj);
+
+ obj = &rk_obj->base;
+
+ /*
+ * allocate a id of idr table where the obj is registered
+ * and handle has the id what user can see.
+ */
+ ret = drm_gem_handle_create(file_priv, obj, handle);
+ if (ret)
+ goto err_handle_create;
+
+ /* drop reference from allocate - handle holds it now. */
+ drm_gem_object_unreference_unlocked(obj);
+
+ return rk_obj;
+
+err_handle_create:
+ rockchip_gem_free_object(obj);
+
+ return ERR_PTR(ret);
+}
+
+int rockchip_gem_dumb_map_offset(struct drm_file *file_priv,
+ struct drm_device *dev, uint32_t handle,
+ uint64_t *offset)
+{
+ struct drm_gem_object *obj;
+ int ret;
+
+ mutex_lock(&dev->struct_mutex);
+
+ obj = drm_gem_object_lookup(dev, file_priv, handle);
+ if (!obj) {
+ DRM_ERROR("failed to lookup gem object.\n");
+ ret = -EINVAL;
+ goto unlock;
+ }
+
+ ret = drm_gem_create_mmap_offset(obj);
+ if (ret)
+ goto out;
+
+ *offset = drm_vma_node_offset_addr(&obj->vma_node);
+ DRM_DEBUG_KMS("offset = 0x%llx\n", *offset);
+
+out:
+ drm_gem_object_unreference(obj);
+unlock:
+ mutex_unlock(&dev->struct_mutex);
+ return ret;
+}
+
+/*
+ * rockchip_gem_dumb_create - (struct drm_driver)->dumb_create callback
+ * function
+ *
+ * This aligns the pitch and size arguments to the minimum required. wrap
+ * this into your own function if you need bigger alignment.
+ */
+int rockchip_gem_dumb_create(struct drm_file *file_priv,
+ struct drm_device *dev,
+ struct drm_mode_create_dumb *args)
+{
+ struct rockchip_gem_object *rk_obj;
+ int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
+
+ /*
+ * align to 64 bytes since Mali requires it.
+ */
+ min_pitch = ALIGN(min_pitch, 64);
+
+ if (args->pitch < min_pitch)
+ args->pitch = min_pitch;
+
+ if (args->size < args->pitch * args->height)
+ args->size = args->pitch * args->height;
+
+ rk_obj = rockchip_gem_create_with_handle(file_priv, dev, args->size,
+ &args->handle);
+
+ return PTR_ERR_OR_ZERO(rk_obj);
+}
+
+/*
+ * Allocate a sg_table for this GEM object.
+ * Note: Both the table's contents, and the sg_table itself must be freed by
+ * the caller.
+ * Returns a pointer to the newly allocated sg_table, or an ERR_PTR() error.
+ */
+struct sg_table *rockchip_gem_prime_get_sg_table(struct drm_gem_object *obj)
+{
+ struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
+ struct drm_device *drm = obj->dev;
+ struct sg_table *sgt;
+ int ret;
+
+ sgt = kzalloc(sizeof(*sgt), GFP_KERNEL);
+ if (!sgt)
+ return ERR_PTR(-ENOMEM);
+
+ ret = dma_get_sgtable_attrs(drm->dev, sgt, rk_obj->kvaddr,
+ rk_obj->dma_addr, obj->size,
+ &rk_obj->dma_attrs);
+ if (ret) {
+ DRM_ERROR("failed to allocate sgt, %d\n", ret);
+ kfree(sgt);
+ return ERR_PTR(ret);
+ }
+
+ return sgt;
+}
+
+void *rockchip_gem_prime_vmap(struct drm_gem_object *obj)
+{
+ struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
+
+ return rk_obj->kvaddr;
+}
+
+void rockchip_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr)
+{
+ /* Nothing to do */
+}
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_gem.h b/drivers/gpu/drm/rockchip/rockchip_drm_gem.h
new file mode 100644
index 0000000..67bcebe
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_gem.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao@rock-chips.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _ROCKCHIP_DRM_GEM_H
+#define _ROCKCHIP_DRM_GEM_H
+
+#define to_rockchip_obj(x) container_of(x, struct rockchip_gem_object, base)
+
+struct rockchip_gem_object {
+ struct drm_gem_object base;
+ unsigned int flags;
+
+ void *kvaddr;
+ dma_addr_t dma_addr;
+ struct dma_attrs dma_attrs;
+};
+
+struct sg_table *rockchip_gem_prime_get_sg_table(struct drm_gem_object *obj);
+struct drm_gem_object *
+rockchip_gem_prime_import_sg_table(struct drm_device *dev, size_t size,
+ struct sg_table *sgt);
+void *rockchip_gem_prime_vmap(struct drm_gem_object *obj);
+void rockchip_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr);
+
+/* drm driver mmap file operations */
+int rockchip_gem_mmap(struct file *filp, struct vm_area_struct *vma);
+
+/* mmap a gem object to userspace. */
+int rockchip_gem_mmap_buf(struct drm_gem_object *obj,
+ struct vm_area_struct *vma);
+
+struct rockchip_gem_object *
+ rockchip_gem_create_object(struct drm_device *drm, unsigned int size);
+
+void rockchip_gem_free_object(struct drm_gem_object *obj);
+
+int rockchip_gem_dumb_create(struct drm_file *file_priv,
+ struct drm_device *dev,
+ struct drm_mode_create_dumb *args);
+int rockchip_gem_dumb_map_offset(struct drm_file *file_priv,
+ struct drm_device *dev, uint32_t handle,
+ uint64_t *offset);
+#endif /* _ROCKCHIP_DRM_GEM_H */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
new file mode 100644
index 0000000..e7ca25b
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
@@ -0,0 +1,1455 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao@rock-chips.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <drm/drm.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_plane_helper.h>
+
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/component.h>
+
+#include <linux/reset.h>
+#include <linux/delay.h>
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_gem.h"
+#include "rockchip_drm_fb.h"
+#include "rockchip_drm_vop.h"
+
+#define VOP_REG(off, _mask, s) \
+ {.offset = off, \
+ .mask = _mask, \
+ .shift = s,}
+
+#define __REG_SET_RELAXED(x, off, mask, shift, v) \
+ vop_mask_write_relaxed(x, off, (mask) << shift, (v) << shift)
+#define __REG_SET_NORMAL(x, off, mask, shift, v) \
+ vop_mask_write(x, off, (mask) << shift, (v) << shift)
+
+#define REG_SET(x, base, reg, v, mode) \
+ __REG_SET_##mode(x, base + reg.offset, reg.mask, reg.shift, v)
+
+#define VOP_WIN_SET(x, win, name, v) \
+ REG_SET(x, win->base, win->phy->name, v, RELAXED)
+#define VOP_CTRL_SET(x, name, v) \
+ REG_SET(x, 0, (x)->data->ctrl->name, v, NORMAL)
+
+#define VOP_WIN_GET(x, win, name) \
+ vop_read_reg(x, win->base, &win->phy->name)
+
+#define VOP_WIN_GET_YRGBADDR(vop, win) \
+ vop_readl(vop, win->base + win->phy->yrgb_mst.offset)
+
+#define to_vop(x) container_of(x, struct vop, crtc)
+#define to_vop_win(x) container_of(x, struct vop_win, base)
+
+struct vop_win_state {
+ struct list_head head;
+ struct drm_framebuffer *fb;
+ dma_addr_t yrgb_mst;
+ struct drm_pending_vblank_event *event;
+};
+
+struct vop_win {
+ struct drm_plane base;
+ const struct vop_win_data *data;
+ struct vop *vop;
+
+ struct list_head pending;
+ struct vop_win_state *active;
+};
+
+struct vop {
+ struct drm_crtc crtc;
+ struct device *dev;
+ struct drm_device *drm_dev;
+ unsigned int dpms;
+
+ int connector_type;
+ int connector_out_mode;
+
+ /* mutex vsync_ work */
+ struct mutex vsync_mutex;
+ bool vsync_work_pending;
+
+ const struct vop_data *data;
+
+ uint32_t *regsbak;
+ void __iomem *regs;
+
+ /* physical map length of vop register */
+ uint32_t len;
+
+ /* one time only one process allowed to config the register */
+ spinlock_t reg_lock;
+ /* lock vop irq reg */
+ spinlock_t irq_lock;
+
+ unsigned int irq;
+
+ /* vop AHP clk */
+ struct clk *hclk;
+ /* vop dclk */
+ struct clk *dclk;
+ /* vop share memory frequency */
+ struct clk *aclk;
+
+ /* vop dclk reset */
+ struct reset_control *dclk_rst;
+
+ int pipe;
+
+ struct vop_win win[];
+};
+
+enum vop_data_format {
+ VOP_FMT_ARGB8888 = 0,
+ VOP_FMT_RGB888,
+ VOP_FMT_RGB565,
+ VOP_FMT_YUV420SP = 4,
+ VOP_FMT_YUV422SP,
+ VOP_FMT_YUV444SP,
+};
+
+struct vop_reg_data {
+ uint32_t offset;
+ uint32_t value;
+};
+
+struct vop_reg {
+ uint32_t offset;
+ uint32_t shift;
+ uint32_t mask;
+};
+
+struct vop_ctrl {
+ struct vop_reg standby;
+ struct vop_reg data_blank;
+ struct vop_reg gate_en;
+ struct vop_reg mmu_en;
+ struct vop_reg rgb_en;
+ struct vop_reg edp_en;
+ struct vop_reg hdmi_en;
+ struct vop_reg mipi_en;
+ struct vop_reg out_mode;
+ struct vop_reg dither_down;
+ struct vop_reg dither_up;
+ struct vop_reg pin_pol;
+
+ struct vop_reg htotal_pw;
+ struct vop_reg hact_st_end;
+ struct vop_reg vtotal_pw;
+ struct vop_reg vact_st_end;
+ struct vop_reg hpost_st_end;
+ struct vop_reg vpost_st_end;
+};
+
+struct vop_win_phy {
+ const uint32_t *data_formats;
+ uint32_t nformats;
+
+ struct vop_reg enable;
+ struct vop_reg format;
+ struct vop_reg act_info;
+ struct vop_reg dsp_info;
+ struct vop_reg dsp_st;
+ struct vop_reg yrgb_mst;
+ struct vop_reg uv_mst;
+ struct vop_reg yrgb_vir;
+ struct vop_reg uv_vir;
+
+ struct vop_reg dst_alpha_ctl;
+ struct vop_reg src_alpha_ctl;
+};
+
+struct vop_win_data {
+ uint32_t base;
+ const struct vop_win_phy *phy;
+ enum drm_plane_type type;
+};
+
+struct vop_data {
+ const struct vop_reg_data *init_table;
+ unsigned int table_size;
+ const struct vop_ctrl *ctrl;
+ const struct vop_win_data *win;
+ unsigned int win_size;
+};
+
+static const uint32_t formats_01[] = {
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_NV12,
+ DRM_FORMAT_NV16,
+ DRM_FORMAT_NV24,
+};
+
+static const uint32_t formats_234[] = {
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_RGB565,
+};
+
+static const struct vop_win_phy win01_data = {
+ .data_formats = formats_01,
+ .nformats = ARRAY_SIZE(formats_01),
+ .enable = VOP_REG(WIN0_CTRL0, 0x1, 0),
+ .format = VOP_REG(WIN0_CTRL0, 0x7, 1),
+ .act_info = VOP_REG(WIN0_ACT_INFO, 0x1fff1fff, 0),
+ .dsp_info = VOP_REG(WIN0_DSP_INFO, 0x0fff0fff, 0),
+ .dsp_st = VOP_REG(WIN0_DSP_ST, 0x1fff1fff, 0),
+ .yrgb_mst = VOP_REG(WIN0_YRGB_MST, 0xffffffff, 0),
+ .uv_mst = VOP_REG(WIN0_CBR_MST, 0xffffffff, 0),
+ .yrgb_vir = VOP_REG(WIN0_VIR, 0x3fff, 0),
+ .uv_vir = VOP_REG(WIN0_VIR, 0x3fff, 16),
+ .src_alpha_ctl = VOP_REG(WIN0_SRC_ALPHA_CTRL, 0xff, 0),
+ .dst_alpha_ctl = VOP_REG(WIN0_DST_ALPHA_CTRL, 0xff, 0),
+};
+
+static const struct vop_win_phy win23_data = {
+ .data_formats = formats_234,
+ .nformats = ARRAY_SIZE(formats_234),
+ .enable = VOP_REG(WIN2_CTRL0, 0x1, 0),
+ .format = VOP_REG(WIN2_CTRL0, 0x7, 1),
+ .dsp_info = VOP_REG(WIN2_DSP_INFO0, 0x0fff0fff, 0),
+ .dsp_st = VOP_REG(WIN2_DSP_ST0, 0x1fff1fff, 0),
+ .yrgb_mst = VOP_REG(WIN2_MST0, 0xffffffff, 0),
+ .yrgb_vir = VOP_REG(WIN2_VIR0_1, 0x1fff, 0),
+ .src_alpha_ctl = VOP_REG(WIN2_SRC_ALPHA_CTRL, 0xff, 0),
+ .dst_alpha_ctl = VOP_REG(WIN2_DST_ALPHA_CTRL, 0xff, 0),
+};
+
+static const struct vop_win_phy cursor_data = {
+ .data_formats = formats_234,
+ .nformats = ARRAY_SIZE(formats_234),
+ .enable = VOP_REG(HWC_CTRL0, 0x1, 0),
+ .format = VOP_REG(HWC_CTRL0, 0x7, 1),
+ .dsp_st = VOP_REG(HWC_DSP_ST, 0x1fff1fff, 0),
+ .yrgb_mst = VOP_REG(HWC_MST, 0xffffffff, 0),
+};
+
+static const struct vop_ctrl ctrl_data = {
+ .standby = VOP_REG(SYS_CTRL, 0x1, 22),
+ .gate_en = VOP_REG(SYS_CTRL, 0x1, 23),
+ .mmu_en = VOP_REG(SYS_CTRL, 0x1, 20),
+ .rgb_en = VOP_REG(SYS_CTRL, 0x1, 12),
+ .hdmi_en = VOP_REG(SYS_CTRL, 0x1, 13),
+ .edp_en = VOP_REG(SYS_CTRL, 0x1, 14),
+ .mipi_en = VOP_REG(SYS_CTRL, 0x1, 15),
+ .dither_down = VOP_REG(DSP_CTRL1, 0xf, 1),
+ .dither_up = VOP_REG(DSP_CTRL1, 0x1, 6),
+ .data_blank = VOP_REG(DSP_CTRL0, 0x1, 19),
+ .out_mode = VOP_REG(DSP_CTRL0, 0xf, 0),
+ .pin_pol = VOP_REG(DSP_CTRL0, 0xf, 4),
+ .htotal_pw = VOP_REG(DSP_HTOTAL_HS_END, 0x1fff1fff, 0),
+ .hact_st_end = VOP_REG(DSP_HACT_ST_END, 0x1fff1fff, 0),
+ .vtotal_pw = VOP_REG(DSP_VTOTAL_VS_END, 0x1fff1fff, 0),
+ .vact_st_end = VOP_REG(DSP_VACT_ST_END, 0x1fff1fff, 0),
+ .hpost_st_end = VOP_REG(POST_DSP_HACT_INFO, 0x1fff1fff, 0),
+ .vpost_st_end = VOP_REG(POST_DSP_VACT_INFO, 0x1fff1fff, 0),
+};
+
+static const struct vop_reg_data vop_init_reg_table[] = {
+ {SYS_CTRL, 0x00c00000},
+ {DSP_CTRL0, 0x00000000},
+ {WIN0_CTRL0, 0x00000080},
+ {WIN1_CTRL0, 0x00000080},
+};
+
+/*
+ * Note: rk3288 has a dedicated 'cursor' window, however, that window requires
+ * special support to get alpha blending working. For now, just use overlay
+ * window 1 for the drm cursor.
+ */
+static const struct vop_win_data rk3288_vop_win_data[] = {
+ { .base = 0x00, .phy = &win01_data, .type = DRM_PLANE_TYPE_PRIMARY },
+ { .base = 0x40, .phy = &win01_data, .type = DRM_PLANE_TYPE_CURSOR },
+ { .base = 0x00, .phy = &win23_data, .type = DRM_PLANE_TYPE_OVERLAY },
+ { .base = 0x50, .phy = &win23_data, .type = DRM_PLANE_TYPE_OVERLAY },
+ { .base = 0x00, .phy = &cursor_data, .type = DRM_PLANE_TYPE_OVERLAY },
+};
+
+static const struct vop_data rk3288_vop = {
+ .init_table = vop_init_reg_table,
+ .table_size = ARRAY_SIZE(vop_init_reg_table),
+ .ctrl = &ctrl_data,
+ .win = rk3288_vop_win_data,
+ .win_size = ARRAY_SIZE(rk3288_vop_win_data),
+};
+
+static const struct of_device_id vop_driver_dt_match[] = {
+ { .compatible = "rockchip,rk3288-vop",
+ .data = &rk3288_vop },
+ {},
+};
+
+static inline void vop_writel(struct vop *vop, uint32_t offset, uint32_t v)
+{
+ writel(v, vop->regs + offset);
+ vop->regsbak[offset >> 2] = v;
+}
+
+static inline uint32_t vop_readl(struct vop *vop, uint32_t offset)
+{
+ return readl(vop->regs + offset);
+}
+
+static inline uint32_t vop_read_reg(struct vop *vop, uint32_t base,
+ const struct vop_reg *reg)
+{
+ return (vop_readl(vop, base + reg->offset) >> reg->shift) & reg->mask;
+}
+
+static inline void vop_cfg_done(struct vop *vop)
+{
+ writel(0x01, vop->regs + REG_CFG_DONE);
+}
+
+static inline void vop_mask_write(struct vop *vop, uint32_t offset,
+ uint32_t mask, uint32_t v)
+{
+ if (mask) {
+ uint32_t cached_val = vop->regsbak[offset >> 2];
+
+ cached_val = (cached_val & ~mask) | v;
+ writel(cached_val, vop->regs + offset);
+ vop->regsbak[offset >> 2] = cached_val;
+ }
+}
+
+static inline void vop_mask_write_relaxed(struct vop *vop, uint32_t offset,
+ uint32_t mask, uint32_t v)
+{
+ if (mask) {
+ uint32_t cached_val = vop->regsbak[offset >> 2];
+
+ cached_val = (cached_val & ~mask) | v;
+ writel_relaxed(cached_val, vop->regs + offset);
+ vop->regsbak[offset >> 2] = cached_val;
+ }
+}
+
+static enum vop_data_format vop_convert_format(uint32_t format)
+{
+ switch (format) {
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ARGB8888:
+ return VOP_FMT_ARGB8888;
+ case DRM_FORMAT_RGB888:
+ return VOP_FMT_RGB888;
+ case DRM_FORMAT_RGB565:
+ return VOP_FMT_RGB565;
+ case DRM_FORMAT_NV12:
+ return VOP_FMT_YUV420SP;
+ case DRM_FORMAT_NV16:
+ return VOP_FMT_YUV422SP;
+ case DRM_FORMAT_NV24:
+ return VOP_FMT_YUV444SP;
+ default:
+ DRM_ERROR("unsupport format[%08x]\n", format);
+ return -EINVAL;
+ }
+}
+
+static bool is_alpha_support(uint32_t format)
+{
+ switch (format) {
+ case DRM_FORMAT_ARGB8888:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void vop_enable(struct drm_crtc *crtc)
+{
+ struct vop *vop = to_vop(crtc);
+ int ret;
+
+ ret = clk_enable(vop->hclk);
+ if (ret < 0) {
+ dev_err(vop->dev, "failed to enable hclk - %d\n", ret);
+ return;
+ }
+
+ ret = clk_enable(vop->dclk);
+ if (ret < 0) {
+ dev_err(vop->dev, "failed to enable dclk - %d\n", ret);
+ goto err_disable_hclk;
+ }
+
+ ret = clk_enable(vop->aclk);
+ if (ret < 0) {
+ dev_err(vop->dev, "failed to enable aclk - %d\n", ret);
+ goto err_disable_dclk;
+ }
+
+ /*
+ * Slave iommu shares power, irq and clock with vop. It was associated
+ * automatically with this master device via common driver code.
+ * Now that we have enabled the clock we attach it to the shared drm
+ * mapping.
+ */
+ ret = rockchip_drm_dma_attach_device(vop->drm_dev, vop->dev);
+ if (ret) {
+ dev_err(vop->dev, "failed to attach dma mapping, %d\n", ret);
+ goto err_disable_aclk;
+ }
+
+ spin_lock(&vop->reg_lock);
+
+ VOP_CTRL_SET(vop, standby, 0);
+
+ spin_unlock(&vop->reg_lock);
+
+ enable_irq(vop->irq);
+
+ drm_vblank_on(vop->drm_dev, vop->pipe);
+
+ return;
+
+err_disable_aclk:
+ clk_disable(vop->aclk);
+err_disable_dclk:
+ clk_disable(vop->dclk);
+err_disable_hclk:
+ clk_disable(vop->hclk);
+}
+
+static void vop_disable(struct drm_crtc *crtc)
+{
+ struct vop *vop = to_vop(crtc);
+
+ drm_vblank_off(crtc->dev, vop->pipe);
+
+ disable_irq(vop->irq);
+
+ /*
+ * TODO: Since standby doesn't take effect until the next vblank,
+ * when we turn off dclk below, the vop is probably still active.
+ */
+ spin_lock(&vop->reg_lock);
+
+ VOP_CTRL_SET(vop, standby, 1);
+
+ spin_unlock(&vop->reg_lock);
+ /*
+ * disable dclk to stop frame scan, so we can safely detach iommu,
+ */
+ clk_disable(vop->dclk);
+
+ rockchip_drm_dma_detach_device(vop->drm_dev, vop->dev);
+
+ clk_disable(vop->aclk);
+ clk_disable(vop->hclk);
+}
+
+/*
+ * Caller must hold vsync_mutex.
+ */
+static struct drm_framebuffer *vop_win_last_pending_fb(struct vop_win *vop_win)
+{
+ struct vop_win_state *last;
+ struct vop_win_state *active = vop_win->active;
+
+ if (list_empty(&vop_win->pending))
+ return active ? active->fb : NULL;
+
+ last = list_last_entry(&vop_win->pending, struct vop_win_state, head);
+ return last ? last->fb : NULL;
+}
+
+/*
+ * Caller must hold vsync_mutex.
+ */
+static int vop_win_queue_fb(struct vop_win *vop_win,
+ struct drm_framebuffer *fb, dma_addr_t yrgb_mst,
+ struct drm_pending_vblank_event *event)
+{
+ struct vop_win_state *state;
+
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return -ENOMEM;
+
+ state->fb = fb;
+ state->yrgb_mst = yrgb_mst;
+ state->event = event;
+
+ list_add_tail(&state->head, &vop_win->pending);
+
+ return 0;
+}
+
+static int vop_update_plane_event(struct drm_plane *plane,
+ struct drm_crtc *crtc,
+ struct drm_framebuffer *fb, int crtc_x,
+ int crtc_y, unsigned int crtc_w,
+ unsigned int crtc_h, uint32_t src_x,
+ uint32_t src_y, uint32_t src_w,
+ uint32_t src_h,
+ struct drm_pending_vblank_event *event)
+{
+ struct vop_win *vop_win = to_vop_win(plane);
+ const struct vop_win_data *win = vop_win->data;
+ struct vop *vop = to_vop(crtc);
+ struct drm_gem_object *obj;
+ struct rockchip_gem_object *rk_obj;
+ unsigned long offset;
+ unsigned int actual_w;
+ unsigned int actual_h;
+ unsigned int dsp_stx;
+ unsigned int dsp_sty;
+ unsigned int y_vir_stride;
+ dma_addr_t yrgb_mst;
+ enum vop_data_format format;
+ uint32_t val;
+ bool is_alpha;
+ bool visible;
+ int ret;
+ struct drm_rect dest = {
+ .x1 = crtc_x,
+ .y1 = crtc_y,
+ .x2 = crtc_x + crtc_w,
+ .y2 = crtc_y + crtc_h,
+ };
+ struct drm_rect src = {
+ /* 16.16 fixed point */
+ .x1 = src_x,
+ .y1 = src_y,
+ .x2 = src_x + src_w,
+ .y2 = src_y + src_h,
+ };
+ const struct drm_rect clip = {
+ .x2 = crtc->mode.hdisplay,
+ .y2 = crtc->mode.vdisplay,
+ };
+ bool can_position = plane->type != DRM_PLANE_TYPE_PRIMARY;
+
+ ret = drm_plane_helper_check_update(plane, crtc, fb,
+ &src, &dest, &clip,
+ DRM_PLANE_HELPER_NO_SCALING,
+ DRM_PLANE_HELPER_NO_SCALING,
+ can_position, false, &visible);
+ if (ret)
+ return ret;
+
+ if (!visible)
+ return 0;
+
+ is_alpha = is_alpha_support(fb->pixel_format);
+ format = vop_convert_format(fb->pixel_format);
+ if (format < 0)
+ return format;
+
+ obj = rockchip_fb_get_gem_obj(fb, 0);
+ if (!obj) {
+ DRM_ERROR("fail to get rockchip gem object from framebuffer\n");
+ return -EINVAL;
+ }
+
+ rk_obj = to_rockchip_obj(obj);
+
+ actual_w = (src.x2 - src.x1) >> 16;
+ actual_h = (src.y2 - src.y1) >> 16;
+ crtc_x = max(0, crtc_x);
+ crtc_y = max(0, crtc_y);
+
+ dsp_stx = crtc_x + crtc->mode.htotal - crtc->mode.hsync_start;
+ dsp_sty = crtc_y + crtc->mode.vtotal - crtc->mode.vsync_start;
+
+ offset = (src.x1 >> 16) * (fb->bits_per_pixel >> 3);
+ offset += (src.y1 >> 16) * fb->pitches[0];
+ yrgb_mst = rk_obj->dma_addr + offset;
+
+ y_vir_stride = fb->pitches[0] / (fb->bits_per_pixel >> 3);
+
+ /*
+ * If this plane update changes the plane's framebuffer, (or more
+ * precisely, if this update has a different framebuffer than the last
+ * update), enqueue it so we can track when it completes.
+ *
+ * Only when we discover that this update has completed, can we
+ * unreference any previous framebuffers.
+ */
+ mutex_lock(&vop->vsync_mutex);
+ if (fb != vop_win_last_pending_fb(vop_win)) {
+ ret = drm_vblank_get(plane->dev, vop->pipe);
+ if (ret) {
+ DRM_ERROR("failed to get vblank, %d\n", ret);
+ mutex_unlock(&vop->vsync_mutex);
+ return ret;
+ }
+
+ drm_framebuffer_reference(fb);
+
+ ret = vop_win_queue_fb(vop_win, fb, yrgb_mst, event);
+ if (ret) {
+ drm_vblank_put(plane->dev, vop->pipe);
+ mutex_unlock(&vop->vsync_mutex);
+ return ret;
+ }
+
+ vop->vsync_work_pending = true;
+ }
+ mutex_unlock(&vop->vsync_mutex);
+
+ spin_lock(&vop->reg_lock);
+
+ VOP_WIN_SET(vop, win, format, format);
+ VOP_WIN_SET(vop, win, yrgb_vir, y_vir_stride);
+ VOP_WIN_SET(vop, win, yrgb_mst, yrgb_mst);
+ val = (actual_h - 1) << 16;
+ val |= (actual_w - 1) & 0xffff;
+ VOP_WIN_SET(vop, win, act_info, val);
+ VOP_WIN_SET(vop, win, dsp_info, val);
+ val = (dsp_sty - 1) << 16;
+ val |= (dsp_stx - 1) & 0xffff;
+ VOP_WIN_SET(vop, win, dsp_st, val);
+
+ if (is_alpha) {
+ VOP_WIN_SET(vop, win, dst_alpha_ctl,
+ DST_FACTOR_M0(ALPHA_SRC_INVERSE));
+ val = SRC_ALPHA_EN(1) | SRC_COLOR_M0(ALPHA_SRC_PRE_MUL) |
+ SRC_ALPHA_M0(ALPHA_STRAIGHT) |
+ SRC_BLEND_M0(ALPHA_PER_PIX) |
+ SRC_ALPHA_CAL_M0(ALPHA_NO_SATURATION) |
+ SRC_FACTOR_M0(ALPHA_ONE);
+ VOP_WIN_SET(vop, win, src_alpha_ctl, val);
+ } else {
+ VOP_WIN_SET(vop, win, src_alpha_ctl, SRC_ALPHA_EN(0));
+ }
+
+ VOP_WIN_SET(vop, win, enable, 1);
+
+ vop_cfg_done(vop);
+ spin_unlock(&vop->reg_lock);
+
+ return 0;
+}
+
+static int vop_update_plane(struct drm_plane *plane, struct drm_crtc *crtc,
+ struct drm_framebuffer *fb, int crtc_x, int crtc_y,
+ unsigned int crtc_w, unsigned int crtc_h,
+ uint32_t src_x, uint32_t src_y, uint32_t src_w,
+ uint32_t src_h)
+{
+ return vop_update_plane_event(plane, crtc, fb, crtc_x, crtc_y, crtc_w,
+ crtc_h, src_x, src_y, src_w, src_h,
+ NULL);
+}
+
+static int vop_update_primary_plane(struct drm_crtc *crtc,
+ struct drm_pending_vblank_event *event)
+{
+ unsigned int crtc_w, crtc_h;
+
+ crtc_w = crtc->primary->fb->width - crtc->x;
+ crtc_h = crtc->primary->fb->height - crtc->y;
+
+ return vop_update_plane_event(crtc->primary, crtc, crtc->primary->fb,
+ 0, 0, crtc_w, crtc_h, crtc->x << 16,
+ crtc->y << 16, crtc_w << 16,
+ crtc_h << 16, event);
+}
+
+static int vop_disable_plane(struct drm_plane *plane)
+{
+ struct vop_win *vop_win = to_vop_win(plane);
+ const struct vop_win_data *win = vop_win->data;
+ struct vop *vop;
+ int ret;
+
+ if (!plane->crtc)
+ return 0;
+
+ vop = to_vop(plane->crtc);
+
+ ret = drm_vblank_get(plane->dev, vop->pipe);
+ if (ret) {
+ DRM_ERROR("failed to get vblank, %d\n", ret);
+ return ret;
+ }
+
+ mutex_lock(&vop->vsync_mutex);
+
+ ret = vop_win_queue_fb(vop_win, NULL, 0, NULL);
+ if (ret) {
+ drm_vblank_put(plane->dev, vop->pipe);
+ mutex_unlock(&vop->vsync_mutex);
+ return ret;
+ }
+
+ vop->vsync_work_pending = true;
+ mutex_unlock(&vop->vsync_mutex);
+
+ spin_lock(&vop->reg_lock);
+ VOP_WIN_SET(vop, win, enable, 0);
+ vop_cfg_done(vop);
+ spin_unlock(&vop->reg_lock);
+
+ return 0;
+}
+
+static void vop_plane_destroy(struct drm_plane *plane)
+{
+ vop_disable_plane(plane);
+ drm_plane_cleanup(plane);
+}
+
+static const struct drm_plane_funcs vop_plane_funcs = {
+ .update_plane = vop_update_plane,
+ .disable_plane = vop_disable_plane,
+ .destroy = vop_plane_destroy,
+};
+
+int rockchip_drm_crtc_mode_config(struct drm_crtc *crtc,
+ int connector_type,
+ int out_mode)
+{
+ struct vop *vop = to_vop(crtc);
+
+ vop->connector_type = connector_type;
+ vop->connector_out_mode = out_mode;
+
+ return 0;
+}
+
+static int vop_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+ struct vop *vop = to_vop(crtc);
+ unsigned long flags;
+
+ if (vop->dpms != DRM_MODE_DPMS_ON)
+ return -EPERM;
+
+ spin_lock_irqsave(&vop->irq_lock, flags);
+
+ vop_mask_write(vop, INTR_CTRL0, FS_INTR_MASK, FS_INTR_EN(1));
+
+ spin_unlock_irqrestore(&vop->irq_lock, flags);
+
+ return 0;
+}
+
+static void vop_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+ struct vop *vop = to_vop(crtc);
+ unsigned long flags;
+
+ if (vop->dpms != DRM_MODE_DPMS_ON)
+ return;
+ spin_lock_irqsave(&vop->irq_lock, flags);
+ vop_mask_write(vop, INTR_CTRL0, FS_INTR_MASK, FS_INTR_EN(0));
+ spin_unlock_irqrestore(&vop->irq_lock, flags);
+}
+
+static const struct rockchip_crtc_funcs private_crtc_funcs = {
+ .enable_vblank = vop_crtc_enable_vblank,
+ .disable_vblank = vop_crtc_disable_vblank,
+};
+
+static void vop_crtc_dpms(struct drm_crtc *crtc, int mode)
+{
+ struct vop *vop = to_vop(crtc);
+
+ DRM_DEBUG_KMS("crtc[%d] mode[%d]\n", crtc->base.id, mode);
+
+ if (vop->dpms == mode) {
+ DRM_DEBUG_KMS("desired dpms mode is same as previous one.\n");
+ return;
+ }
+
+ switch (mode) {
+ case DRM_MODE_DPMS_ON:
+ vop_enable(crtc);
+ break;
+ case DRM_MODE_DPMS_STANDBY:
+ case DRM_MODE_DPMS_SUSPEND:
+ case DRM_MODE_DPMS_OFF:
+ vop_disable(crtc);
+ break;
+ default:
+ DRM_DEBUG_KMS("unspecified mode %d\n", mode);
+ break;
+ }
+
+ vop->dpms = mode;
+}
+
+static void vop_crtc_prepare(struct drm_crtc *crtc)
+{
+ vop_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
+}
+
+static bool vop_crtc_mode_fixup(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ if (adjusted_mode->htotal == 0 || adjusted_mode->vtotal == 0)
+ return false;
+
+ return true;
+}
+
+static int vop_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
+ struct drm_framebuffer *old_fb)
+{
+ int ret;
+
+ crtc->x = x;
+ crtc->y = y;
+
+ ret = vop_update_primary_plane(crtc, NULL);
+ if (ret < 0) {
+ DRM_ERROR("fail to update plane\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int vop_crtc_mode_set(struct drm_crtc *crtc,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode,
+ int x, int y, struct drm_framebuffer *fb)
+{
+ struct vop *vop = to_vop(crtc);
+ u16 hsync_len = adjusted_mode->hsync_end - adjusted_mode->hsync_start;
+ u16 hdisplay = adjusted_mode->hdisplay;
+ u16 htotal = adjusted_mode->htotal;
+ u16 hact_st = adjusted_mode->htotal - adjusted_mode->hsync_start;
+ u16 hact_end = hact_st + hdisplay;
+ u16 vdisplay = adjusted_mode->vdisplay;
+ u16 vtotal = adjusted_mode->vtotal;
+ u16 vsync_len = adjusted_mode->vsync_end - adjusted_mode->vsync_start;
+ u16 vact_st = adjusted_mode->vtotal - adjusted_mode->vsync_start;
+ u16 vact_end = vact_st + vdisplay;
+ int ret;
+ uint32_t val;
+
+ /*
+ * disable dclk to stop frame scan, so that we can safe config mode and
+ * enable iommu.
+ */
+ clk_disable(vop->dclk);
+
+ switch (vop->connector_type) {
+ case DRM_MODE_CONNECTOR_LVDS:
+ VOP_CTRL_SET(vop, rgb_en, 1);
+ break;
+ case DRM_MODE_CONNECTOR_eDP:
+ VOP_CTRL_SET(vop, edp_en, 1);
+ break;
+ case DRM_MODE_CONNECTOR_HDMIA:
+ VOP_CTRL_SET(vop, hdmi_en, 1);
+ break;
+ default:
+ DRM_ERROR("unsupport connector_type[%d]\n",
+ vop->connector_type);
+ return -EINVAL;
+ };
+ VOP_CTRL_SET(vop, out_mode, vop->connector_out_mode);
+
+ val = 0x8;
+ val |= (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) ? 1 : 0;
+ val |= (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC) ? (1 << 1) : 0;
+ VOP_CTRL_SET(vop, pin_pol, val);
+
+ VOP_CTRL_SET(vop, htotal_pw, (htotal << 16) | hsync_len);
+ val = hact_st << 16;
+ val |= hact_end;
+ VOP_CTRL_SET(vop, hact_st_end, val);
+ VOP_CTRL_SET(vop, hpost_st_end, val);
+
+ VOP_CTRL_SET(vop, vtotal_pw, (vtotal << 16) | vsync_len);
+ val = vact_st << 16;
+ val |= vact_end;
+ VOP_CTRL_SET(vop, vact_st_end, val);
+ VOP_CTRL_SET(vop, vpost_st_end, val);
+
+ ret = vop_crtc_mode_set_base(crtc, x, y, fb);
+ if (ret)
+ return ret;
+
+ /*
+ * reset dclk, take all mode config affect, so the clk would run in
+ * correct frame.
+ */
+ reset_control_assert(vop->dclk_rst);
+ usleep_range(10, 20);
+ reset_control_deassert(vop->dclk_rst);
+
+ clk_set_rate(vop->dclk, adjusted_mode->clock * 1000);
+ ret = clk_enable(vop->dclk);
+ if (ret < 0) {
+ dev_err(vop->dev, "failed to enable dclk - %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void vop_crtc_commit(struct drm_crtc *crtc)
+{
+}
+
+static const struct drm_crtc_helper_funcs vop_crtc_helper_funcs = {
+ .dpms = vop_crtc_dpms,
+ .prepare = vop_crtc_prepare,
+ .mode_fixup = vop_crtc_mode_fixup,
+ .mode_set = vop_crtc_mode_set,
+ .mode_set_base = vop_crtc_mode_set_base,
+ .commit = vop_crtc_commit,
+};
+
+static int vop_crtc_page_flip(struct drm_crtc *crtc,
+ struct drm_framebuffer *fb,
+ struct drm_pending_vblank_event *event,
+ uint32_t page_flip_flags)
+{
+ struct vop *vop = to_vop(crtc);
+ struct drm_framebuffer *old_fb = crtc->primary->fb;
+ int ret;
+
+ /* when the page flip is requested, crtc's dpms should be on */
+ if (vop->dpms > DRM_MODE_DPMS_ON) {
+ DRM_DEBUG("failed page flip request at dpms[%d].\n", vop->dpms);
+ return 0;
+ }
+
+ crtc->primary->fb = fb;
+
+ ret = vop_update_primary_plane(crtc, event);
+ if (ret)
+ crtc->primary->fb = old_fb;
+
+ return ret;
+}
+
+static void vop_win_state_complete(struct vop_win *vop_win,
+ struct vop_win_state *state)
+{
+ struct vop *vop = vop_win->vop;
+ struct drm_crtc *crtc = &vop->crtc;
+ struct drm_device *drm = crtc->dev;
+ unsigned long flags;
+
+ if (state->event) {
+ spin_lock_irqsave(&drm->event_lock, flags);
+ drm_send_vblank_event(drm, -1, state->event);
+ spin_unlock_irqrestore(&drm->event_lock, flags);
+ }
+
+ list_del(&state->head);
+ drm_vblank_put(crtc->dev, vop->pipe);
+}
+
+static void vop_crtc_destroy(struct drm_crtc *crtc)
+{
+ drm_crtc_cleanup(crtc);
+}
+
+static const struct drm_crtc_funcs vop_crtc_funcs = {
+ .set_config = drm_crtc_helper_set_config,
+ .page_flip = vop_crtc_page_flip,
+ .destroy = vop_crtc_destroy,
+};
+
+static bool vop_win_state_is_active(struct vop_win *vop_win,
+ struct vop_win_state *state)
+{
+ bool active = false;
+
+ if (state->fb) {
+ dma_addr_t yrgb_mst;
+
+ /* check yrgb_mst to tell if pending_fb is now front */
+ yrgb_mst = VOP_WIN_GET_YRGBADDR(vop_win->vop, vop_win->data);
+
+ active = (yrgb_mst == state->yrgb_mst);
+ } else {
+ bool enabled;
+
+ /* if enable bit is clear, plane is now disabled */
+ enabled = VOP_WIN_GET(vop_win->vop, vop_win->data, enable);
+
+ active = (enabled == 0);
+ }
+
+ return active;
+}
+
+static void vop_win_state_destroy(struct vop_win_state *state)
+{
+ struct drm_framebuffer *fb = state->fb;
+
+ if (fb)
+ drm_framebuffer_unreference(fb);
+
+ kfree(state);
+}
+
+static void vop_win_update_state(struct vop_win *vop_win)
+{
+ struct vop_win_state *state, *n, *new_active = NULL;
+
+ /* Check if any pending states are now active */
+ list_for_each_entry(state, &vop_win->pending, head)
+ if (vop_win_state_is_active(vop_win, state)) {
+ new_active = state;
+ break;
+ }
+
+ if (!new_active)
+ return;
+
+ /*
+ * Destroy any 'skipped' pending states - states that were queued
+ * before the newly active state.
+ */
+ list_for_each_entry_safe(state, n, &vop_win->pending, head) {
+ if (state == new_active)
+ break;
+ vop_win_state_complete(vop_win, state);
+ vop_win_state_destroy(state);
+ }
+
+ vop_win_state_complete(vop_win, new_active);
+
+ if (vop_win->active)
+ vop_win_state_destroy(vop_win->active);
+ vop_win->active = new_active;
+}
+
+static bool vop_win_has_pending_state(struct vop_win *vop_win)
+{
+ return !list_empty(&vop_win->pending);
+}
+
+static irqreturn_t vop_isr_thread(int irq, void *data)
+{
+ struct vop *vop = data;
+ const struct vop_data *vop_data = vop->data;
+ unsigned int i;
+
+ mutex_lock(&vop->vsync_mutex);
+
+ if (!vop->vsync_work_pending)
+ goto done;
+
+ vop->vsync_work_pending = false;
+
+ for (i = 0; i < vop_data->win_size; i++) {
+ struct vop_win *vop_win = &vop->win[i];
+
+ vop_win_update_state(vop_win);
+ if (vop_win_has_pending_state(vop_win))
+ vop->vsync_work_pending = true;
+ }
+
+done:
+ mutex_unlock(&vop->vsync_mutex);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t vop_isr(int irq, void *data)
+{
+ struct vop *vop = data;
+ uint32_t intr0_reg, active_irqs;
+ unsigned long flags;
+
+ /*
+ * INTR_CTRL0 register has interrupt status, enable and clear bits, we
+ * must hold irq_lock to avoid a race with enable/disable_vblank().
+ */
+ spin_lock_irqsave(&vop->irq_lock, flags);
+ intr0_reg = vop_readl(vop, INTR_CTRL0);
+ active_irqs = intr0_reg & INTR_MASK;
+ /* Clear all active interrupt sources */
+ if (active_irqs)
+ vop_writel(vop, INTR_CTRL0,
+ intr0_reg | (active_irqs << INTR_CLR_SHIFT));
+ spin_unlock_irqrestore(&vop->irq_lock, flags);
+
+ /* This is expected for vop iommu irqs, since the irq is shared */
+ if (!active_irqs)
+ return IRQ_NONE;
+
+ /* Only Frame Start Interrupt is enabled; other irqs are spurious. */
+ if (!(active_irqs & FS_INTR)) {
+ DRM_ERROR("Unknown VOP IRQs: %#02x\n", active_irqs);
+ return IRQ_NONE;
+ }
+
+ drm_handle_vblank(vop->drm_dev, vop->pipe);
+
+ return (vop->vsync_work_pending) ? IRQ_WAKE_THREAD : IRQ_HANDLED;
+}
+
+static int vop_create_crtc(struct vop *vop)
+{
+ const struct vop_data *vop_data = vop->data;
+ struct device *dev = vop->dev;
+ struct drm_device *drm_dev = vop->drm_dev;
+ struct drm_plane *primary = NULL, *cursor = NULL, *plane;
+ struct drm_crtc *crtc = &vop->crtc;
+ struct device_node *port;
+ int ret;
+ int i;
+
+ /*
+ * Create drm_plane for primary and cursor planes first, since we need
+ * to pass them to drm_crtc_init_with_planes, which sets the
+ * "possible_crtcs" to the newly initialized crtc.
+ */
+ for (i = 0; i < vop_data->win_size; i++) {
+ struct vop_win *vop_win = &vop->win[i];
+ const struct vop_win_data *win_data = vop_win->data;
+
+ if (win_data->type != DRM_PLANE_TYPE_PRIMARY &&
+ win_data->type != DRM_PLANE_TYPE_CURSOR)
+ continue;
+
+ ret = drm_universal_plane_init(vop->drm_dev, &vop_win->base,
+ 0, &vop_plane_funcs,
+ win_data->phy->data_formats,
+ win_data->phy->nformats,
+ win_data->type);
+ if (ret) {
+ DRM_ERROR("failed to initialize plane\n");
+ goto err_cleanup_planes;
+ }
+
+ plane = &vop_win->base;
+ if (plane->type == DRM_PLANE_TYPE_PRIMARY)
+ primary = plane;
+ else if (plane->type == DRM_PLANE_TYPE_CURSOR)
+ cursor = plane;
+ }
+
+ ret = drm_crtc_init_with_planes(drm_dev, crtc, primary, cursor,
+ &vop_crtc_funcs);
+ if (ret)
+ return ret;
+
+ drm_crtc_helper_add(crtc, &vop_crtc_helper_funcs);
+
+ /*
+ * Create drm_planes for overlay windows with possible_crtcs restricted
+ * to the newly created crtc.
+ */
+ for (i = 0; i < vop_data->win_size; i++) {
+ struct vop_win *vop_win = &vop->win[i];
+ const struct vop_win_data *win_data = vop_win->data;
+ unsigned long possible_crtcs = 1 << drm_crtc_index(crtc);
+
+ if (win_data->type != DRM_PLANE_TYPE_OVERLAY)
+ continue;
+
+ ret = drm_universal_plane_init(vop->drm_dev, &vop_win->base,
+ possible_crtcs,
+ &vop_plane_funcs,
+ win_data->phy->data_formats,
+ win_data->phy->nformats,
+ win_data->type);
+ if (ret) {
+ DRM_ERROR("failed to initialize overlay plane\n");
+ goto err_cleanup_crtc;
+ }
+ }
+
+ port = of_get_child_by_name(dev->of_node, "port");
+ if (!port) {
+ DRM_ERROR("no port node found in %s\n",
+ dev->of_node->full_name);
+ goto err_cleanup_crtc;
+ }
+
+ crtc->port = port;
+ vop->pipe = drm_crtc_index(crtc);
+ rockchip_register_crtc_funcs(drm_dev, &private_crtc_funcs, vop->pipe);
+
+ return 0;
+
+err_cleanup_crtc:
+ drm_crtc_cleanup(crtc);
+err_cleanup_planes:
+ list_for_each_entry(plane, &drm_dev->mode_config.plane_list, head)
+ drm_plane_cleanup(plane);
+ return ret;
+}
+
+static void vop_destroy_crtc(struct vop *vop)
+{
+ struct drm_crtc *crtc = &vop->crtc;
+
+ rockchip_unregister_crtc_funcs(vop->drm_dev, vop->pipe);
+ of_node_put(crtc->port);
+ drm_crtc_cleanup(crtc);
+}
+
+static int vop_initial(struct vop *vop)
+{
+ const struct vop_data *vop_data = vop->data;
+ const struct vop_reg_data *init_table = vop_data->init_table;
+ struct reset_control *ahb_rst;
+ int i, ret;
+
+ vop->hclk = devm_clk_get(vop->dev, "hclk_vop");
+ if (IS_ERR(vop->hclk)) {
+ dev_err(vop->dev, "failed to get hclk source\n");
+ return PTR_ERR(vop->hclk);
+ }
+ vop->aclk = devm_clk_get(vop->dev, "aclk_vop");
+ if (IS_ERR(vop->aclk)) {
+ dev_err(vop->dev, "failed to get aclk source\n");
+ return PTR_ERR(vop->aclk);
+ }
+ vop->dclk = devm_clk_get(vop->dev, "dclk_vop");
+ if (IS_ERR(vop->dclk)) {
+ dev_err(vop->dev, "failed to get dclk source\n");
+ return PTR_ERR(vop->dclk);
+ }
+
+ ret = clk_prepare(vop->hclk);
+ if (ret < 0) {
+ dev_err(vop->dev, "failed to prepare hclk\n");
+ return ret;
+ }
+
+ ret = clk_prepare(vop->dclk);
+ if (ret < 0) {
+ dev_err(vop->dev, "failed to prepare dclk\n");
+ goto err_unprepare_hclk;
+ }
+
+ ret = clk_prepare(vop->aclk);
+ if (ret < 0) {
+ dev_err(vop->dev, "failed to prepare aclk\n");
+ goto err_unprepare_dclk;
+ }
+
+ /*
+ * enable hclk, so that we can config vop register.
+ */
+ ret = clk_enable(vop->hclk);
+ if (ret < 0) {
+ dev_err(vop->dev, "failed to prepare aclk\n");
+ goto err_unprepare_aclk;
+ }
+ /*
+ * do hclk_reset, reset all vop registers.
+ */
+ ahb_rst = devm_reset_control_get(vop->dev, "ahb");
+ if (IS_ERR(ahb_rst)) {
+ dev_err(vop->dev, "failed to get ahb reset\n");
+ ret = PTR_ERR(ahb_rst);
+ goto err_disable_hclk;
+ }
+ reset_control_assert(ahb_rst);
+ usleep_range(10, 20);
+ reset_control_deassert(ahb_rst);
+
+ memcpy(vop->regsbak, vop->regs, vop->len);
+
+ for (i = 0; i < vop_data->table_size; i++)
+ vop_writel(vop, init_table[i].offset, init_table[i].value);
+
+ for (i = 0; i < vop_data->win_size; i++) {
+ const struct vop_win_data *win = &vop_data->win[i];
+
+ VOP_WIN_SET(vop, win, enable, 0);
+ }
+
+ vop_cfg_done(vop);
+
+ /*
+ * do dclk_reset, let all config take affect.
+ */
+ vop->dclk_rst = devm_reset_control_get(vop->dev, "dclk");
+ if (IS_ERR(vop->dclk_rst)) {
+ dev_err(vop->dev, "failed to get dclk reset\n");
+ ret = PTR_ERR(vop->dclk_rst);
+ goto err_unprepare_aclk;
+ }
+ reset_control_assert(vop->dclk_rst);
+ usleep_range(10, 20);
+ reset_control_deassert(vop->dclk_rst);
+
+ clk_disable(vop->hclk);
+
+ vop->dpms = DRM_MODE_DPMS_OFF;
+
+ return 0;
+
+err_disable_hclk:
+ clk_disable(vop->hclk);
+err_unprepare_aclk:
+ clk_unprepare(vop->aclk);
+err_unprepare_dclk:
+ clk_unprepare(vop->dclk);
+err_unprepare_hclk:
+ clk_unprepare(vop->hclk);
+ return ret;
+}
+
+/*
+ * Initialize the vop->win array elements.
+ */
+static void vop_win_init(struct vop *vop)
+{
+ const struct vop_data *vop_data = vop->data;
+ unsigned int i;
+
+ for (i = 0; i < vop_data->win_size; i++) {
+ struct vop_win *vop_win = &vop->win[i];
+ const struct vop_win_data *win_data = &vop_data->win[i];
+
+ vop_win->data = win_data;
+ vop_win->vop = vop;
+ INIT_LIST_HEAD(&vop_win->pending);
+ }
+}
+
+static int vop_bind(struct device *dev, struct device *master, void *data)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ const struct of_device_id *of_id;
+ const struct vop_data *vop_data;
+ struct drm_device *drm_dev = data;
+ struct vop *vop;
+ struct resource *res;
+ size_t alloc_size;
+ int ret;
+
+ of_id = of_match_device(vop_driver_dt_match, dev);
+ vop_data = of_id->data;
+ if (!vop_data)
+ return -ENODEV;
+
+ /* Allocate vop struct and its vop_win array */
+ alloc_size = sizeof(*vop) + sizeof(*vop->win) * vop_data->win_size;
+ vop = devm_kzalloc(dev, alloc_size, GFP_KERNEL);
+ if (!vop)
+ return -ENOMEM;
+
+ vop->dev = dev;
+ vop->data = vop_data;
+ vop->drm_dev = drm_dev;
+ dev_set_drvdata(dev, vop);
+
+ vop_win_init(vop);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ vop->len = resource_size(res);
+ vop->regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(vop->regs))
+ return PTR_ERR(vop->regs);
+
+ vop->regsbak = devm_kzalloc(dev, vop->len, GFP_KERNEL);
+ if (!vop->regsbak)
+ return -ENOMEM;
+
+ ret = vop_initial(vop);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "cannot initial vop dev - err %d\n", ret);
+ return ret;
+ }
+
+ vop->irq = platform_get_irq(pdev, 0);
+ if (vop->irq < 0) {
+ dev_err(dev, "cannot find irq for vop\n");
+ return vop->irq;
+ }
+
+ spin_lock_init(&vop->reg_lock);
+ spin_lock_init(&vop->irq_lock);
+
+ mutex_init(&vop->vsync_mutex);
+
+ ret = devm_request_threaded_irq(dev, vop->irq, vop_isr, vop_isr_thread,
+ IRQF_SHARED, dev_name(dev), vop);
+ if (ret)
+ return ret;
+
+ /* IRQ is initially disabled; it gets enabled in power_on */
+ disable_irq(vop->irq);
+
+ ret = vop_create_crtc(vop);
+ if (ret)
+ return ret;
+
+ pm_runtime_enable(&pdev->dev);
+ return 0;
+}
+
+static void vop_unbind(struct device *dev, struct device *master, void *data)
+{
+ struct vop *vop = dev_get_drvdata(dev);
+
+ pm_runtime_disable(dev);
+ vop_destroy_crtc(vop);
+}
+
+static const struct component_ops vop_component_ops = {
+ .bind = vop_bind,
+ .unbind = vop_unbind,
+};
+
+static int vop_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ if (!dev->of_node) {
+ dev_err(dev, "can't find vop devices\n");
+ return -ENODEV;
+ }
+
+ return component_add(dev, &vop_component_ops);
+}
+
+static int vop_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &vop_component_ops);
+
+ return 0;
+}
+
+struct platform_driver vop_platform_driver = {
+ .probe = vop_probe,
+ .remove = vop_remove,
+ .driver = {
+ .name = "rockchip-vop",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(vop_driver_dt_match),
+ },
+};
+
+module_platform_driver(vop_platform_driver);
+
+MODULE_AUTHOR("Mark Yao <mark.yao@rock-chips.com>");
+MODULE_DESCRIPTION("ROCKCHIP VOP Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
new file mode 100644
index 0000000..63e9b3a
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao@rock-chips.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _ROCKCHIP_DRM_VOP_H
+#define _ROCKCHIP_DRM_VOP_H
+
+/* register definition */
+#define REG_CFG_DONE 0x0000
+#define VERSION_INFO 0x0004
+#define SYS_CTRL 0x0008
+#define SYS_CTRL1 0x000c
+#define DSP_CTRL0 0x0010
+#define DSP_CTRL1 0x0014
+#define DSP_BG 0x0018
+#define MCU_CTRL 0x001c
+#define INTR_CTRL0 0x0020
+#define INTR_CTRL1 0x0024
+#define WIN0_CTRL0 0x0030
+#define WIN0_CTRL1 0x0034
+#define WIN0_COLOR_KEY 0x0038
+#define WIN0_VIR 0x003c
+#define WIN0_YRGB_MST 0x0040
+#define WIN0_CBR_MST 0x0044
+#define WIN0_ACT_INFO 0x0048
+#define WIN0_DSP_INFO 0x004c
+#define WIN0_DSP_ST 0x0050
+#define WIN0_SCL_FACTOR_YRGB 0x0054
+#define WIN0_SCL_FACTOR_CBR 0x0058
+#define WIN0_SCL_OFFSET 0x005c
+#define WIN0_SRC_ALPHA_CTRL 0x0060
+#define WIN0_DST_ALPHA_CTRL 0x0064
+#define WIN0_FADING_CTRL 0x0068
+/* win1 register */
+#define WIN1_CTRL0 0x0070
+#define WIN1_CTRL1 0x0074
+#define WIN1_COLOR_KEY 0x0078
+#define WIN1_VIR 0x007c
+#define WIN1_YRGB_MST 0x0080
+#define WIN1_CBR_MST 0x0084
+#define WIN1_ACT_INFO 0x0088
+#define WIN1_DSP_INFO 0x008c
+#define WIN1_DSP_ST 0x0090
+#define WIN1_SCL_FACTOR_YRGB 0x0094
+#define WIN1_SCL_FACTOR_CBR 0x0098
+#define WIN1_SCL_OFFSET 0x009c
+#define WIN1_SRC_ALPHA_CTRL 0x00a0
+#define WIN1_DST_ALPHA_CTRL 0x00a4
+#define WIN1_FADING_CTRL 0x00a8
+/* win2 register */
+#define WIN2_CTRL0 0x00b0
+#define WIN2_CTRL1 0x00b4
+#define WIN2_VIR0_1 0x00b8
+#define WIN2_VIR2_3 0x00bc
+#define WIN2_MST0 0x00c0
+#define WIN2_DSP_INFO0 0x00c4
+#define WIN2_DSP_ST0 0x00c8
+#define WIN2_COLOR_KEY 0x00cc
+#define WIN2_MST1 0x00d0
+#define WIN2_DSP_INFO1 0x00d4
+#define WIN2_DSP_ST1 0x00d8
+#define WIN2_SRC_ALPHA_CTRL 0x00dc
+#define WIN2_MST2 0x00e0
+#define WIN2_DSP_INFO2 0x00e4
+#define WIN2_DSP_ST2 0x00e8
+#define WIN2_DST_ALPHA_CTRL 0x00ec
+#define WIN2_MST3 0x00f0
+#define WIN2_DSP_INFO3 0x00f4
+#define WIN2_DSP_ST3 0x00f8
+#define WIN2_FADING_CTRL 0x00fc
+/* win3 register */
+#define WIN3_CTRL0 0x0100
+#define WIN3_CTRL1 0x0104
+#define WIN3_VIR0_1 0x0108
+#define WIN3_VIR2_3 0x010c
+#define WIN3_MST0 0x0110
+#define WIN3_DSP_INFO0 0x0114
+#define WIN3_DSP_ST0 0x0118
+#define WIN3_COLOR_KEY 0x011c
+#define WIN3_MST1 0x0120
+#define WIN3_DSP_INFO1 0x0124
+#define WIN3_DSP_ST1 0x0128
+#define WIN3_SRC_ALPHA_CTRL 0x012c
+#define WIN3_MST2 0x0130
+#define WIN3_DSP_INFO2 0x0134
+#define WIN3_DSP_ST2 0x0138
+#define WIN3_DST_ALPHA_CTRL 0x013c
+#define WIN3_MST3 0x0140
+#define WIN3_DSP_INFO3 0x0144
+#define WIN3_DSP_ST3 0x0148
+#define WIN3_FADING_CTRL 0x014c
+/* hwc register */
+#define HWC_CTRL0 0x0150
+#define HWC_CTRL1 0x0154
+#define HWC_MST 0x0158
+#define HWC_DSP_ST 0x015c
+#define HWC_SRC_ALPHA_CTRL 0x0160
+#define HWC_DST_ALPHA_CTRL 0x0164
+#define HWC_FADING_CTRL 0x0168
+/* post process register */
+#define POST_DSP_HACT_INFO 0x0170
+#define POST_DSP_VACT_INFO 0x0174
+#define POST_SCL_FACTOR_YRGB 0x0178
+#define POST_SCL_CTRL 0x0180
+#define POST_DSP_VACT_INFO_F1 0x0184
+#define DSP_HTOTAL_HS_END 0x0188
+#define DSP_HACT_ST_END 0x018c
+#define DSP_VTOTAL_VS_END 0x0190
+#define DSP_VACT_ST_END 0x0194
+#define DSP_VS_ST_END_F1 0x0198
+#define DSP_VACT_ST_END_F1 0x019c
+/* register definition end */
+
+/* interrupt define */
+#define DSP_HOLD_VALID_INTR (1 << 0)
+#define FS_INTR (1 << 1)
+#define LINE_FLAG_INTR (1 << 2)
+#define BUS_ERROR_INTR (1 << 3)
+
+#define INTR_MASK (DSP_HOLD_VALID_INTR | FS_INTR | \
+ LINE_FLAG_INTR | BUS_ERROR_INTR)
+
+#define DSP_HOLD_VALID_INTR_EN(x) ((x) << 4)
+#define FS_INTR_EN(x) ((x) << 5)
+#define LINE_FLAG_INTR_EN(x) ((x) << 6)
+#define BUS_ERROR_INTR_EN(x) ((x) << 7)
+#define DSP_HOLD_VALID_INTR_MASK (1 << 4)
+#define FS_INTR_MASK (1 << 5)
+#define LINE_FLAG_INTR_MASK (1 << 6)
+#define BUS_ERROR_INTR_MASK (1 << 7)
+
+#define INTR_CLR_SHIFT 8
+#define DSP_HOLD_VALID_INTR_CLR (1 << (INTR_CLR_SHIFT + 0))
+#define FS_INTR_CLR (1 << (INTR_CLR_SHIFT + 1))
+#define LINE_FLAG_INTR_CLR (1 << (INTR_CLR_SHIFT + 2))
+#define BUS_ERROR_INTR_CLR (1 << (INTR_CLR_SHIFT + 3))
+
+#define DSP_LINE_NUM(x) (((x) & 0x1fff) << 12)
+#define DSP_LINE_NUM_MASK (0x1fff << 12)
+
+/* src alpha ctrl define */
+#define SRC_FADING_VALUE(x) (((x) & 0xff) << 24)
+#define SRC_GLOBAL_ALPHA(x) (((x) & 0xff) << 16)
+#define SRC_FACTOR_M0(x) (((x) & 0x7) << 6)
+#define SRC_ALPHA_CAL_M0(x) (((x) & 0x1) << 5)
+#define SRC_BLEND_M0(x) (((x) & 0x3) << 3)
+#define SRC_ALPHA_M0(x) (((x) & 0x1) << 2)
+#define SRC_COLOR_M0(x) (((x) & 0x1) << 1)
+#define SRC_ALPHA_EN(x) (((x) & 0x1) << 0)
+/* dst alpha ctrl define */
+#define DST_FACTOR_M0(x) (((x) & 0x7) << 6)
+
+/*
+ * display output interface supported by rockchip lcdc
+ */
+#define ROCKCHIP_OUT_MODE_P888 0
+#define ROCKCHIP_OUT_MODE_P666 1
+#define ROCKCHIP_OUT_MODE_P565 2
+/* for use special outface */
+#define ROCKCHIP_OUT_MODE_AAAA 15
+
+enum alpha_mode {
+ ALPHA_STRAIGHT,
+ ALPHA_INVERSE,
+};
+
+enum global_blend_mode {
+ ALPHA_GLOBAL,
+ ALPHA_PER_PIX,
+ ALPHA_PER_PIX_GLOBAL,
+};
+
+enum alpha_cal_mode {
+ ALPHA_SATURATION,
+ ALPHA_NO_SATURATION,
+};
+
+enum color_mode {
+ ALPHA_SRC_PRE_MUL,
+ ALPHA_SRC_NO_PRE_MUL,
+};
+
+enum factor_mode {
+ ALPHA_ZERO,
+ ALPHA_ONE,
+ ALPHA_SRC,
+ ALPHA_SRC_INVERSE,
+ ALPHA_SRC_GLOBAL,
+};
+
+#endif /* _ROCKCHIP_DRM_VOP_H */
--
1.7.9.5
^ permalink raw reply related
* [PATCH v15 0/3] Add drm driver for Rockchip Socs
From: Mark Yao @ 2014-12-02 9:13 UTC (permalink / raw)
To: heiko-4mtYJXux2i+zQB+pC5nmwQ, Boris BREZILLON, David Airlie,
Rob Clark, Daniel Vetter, Rob Herring, Pawel Moll, Mark Rutland,
Ian Campbell, Kumar Gala, Randy Dunlap, Grant Likely,
Greg Kroah-Hartman, John Stultz, Rom Lemarchand
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
linux-api-u79uwXL29TY76Z2rM5mHXA,
linux-rockchip-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
dianders-F7+t8E8rja9g9hUCZPvPmw, marcheu-F7+t8E8rja9g9hUCZPvPmw,
dbehr-F7+t8E8rja9g9hUCZPvPmw, olof-nZhT3qVonbNeoWH0uzbU5w,
djkurtz-F7+t8E8rja9g9hUCZPvPmw, cf-TNX95d0MmH7DzftRWevZcw,
xxm-TNX95d0MmH7DzftRWevZcw, huangtao-TNX95d0MmH7DzftRWevZcw,
kever.yang-TNX95d0MmH7DzftRWevZcw, yxj-TNX95d0MmH7DzftRWevZcw,
xw-TNX95d0MmH7DzftRWevZcw, Mark Yao
This a series of patches is a DRM Driver for Rockchip Socs, add support
for vop devices. Future patches will add additional encoders/connectors,
such as eDP, HDMI.
The basic "crtc" for rockchip is a "VOP" - Video Output Processor.
the vop devices found on Rockchip rk3288 Soc, rk3288 soc have two similar
Vop devices. Vop devices support iommu mapping, we use dma-mapping API with
ARM_DMA_USE_IOMMU.
Changes in v2:
- add DRM master device node to list all display nodes that comprise
the graphics subsystem.
- use the component framework to defer main drm driver probe
until all VOP devices have been probed.
- use dma-mapping API with ARM_DMA_USE_IOMMU, create dma mapping by
master device and each vop device can shared the drm dma mapping.
- use drm_crtc_init_with_planes and drm_universal_plane_init.
- remove unnecessary middle layers.
- add cursor set, move funcs to rockchip drm crtc.
- add vop reset.
Changes in v3:
- change "crtc->fb" to "crtc->primary-fb"
Adviced by Daniel Vetter
- init cursor plane with universal api, remove unnecessary cursor set,move
Changes in v4:
Adviced by David Herrmann
- remove drm_platform_*() usage, use register drm device directly.
Adviced by Rob Clark
- remove special mmap ioctl, do userspace mmap with normal mmap() or mmap offset
Changes in v5:
Adviced by Arnd Bergmann
- doing DMA start with a 32-bit masks with dma_mask and dma_coherent_mark
- fix some incorrect dependencies.
Adviced by Boris BREZILLON
- fix some mistake and bugs.
Adviced by Daniel Vetter
- drop all special ioctl and use generic kms ioctl instead.
Adviced by Rob Clark
- use unlocked api for drm_fb_helper_restore_fbdev_mode.
- remove unused rockchip_gem_prime_import_sg_table.
Changes in v6:
- set gem buffer pitch 64 bytes align, needed by mali gpu.
Adviced by Daniel Kurtz
- fix some mistake, bugs, remove unused define, more better code style etc.
- use clk_prepare()/unprepare() at probe()/remove() and clk_enable()/disable()
at runtime instead of clk_prepare_enable().
- provide a help function from vop for encoder to do mode config, instead of
using drm_diaplay_mode private method.
- change vop mode_set timing to make it more safely.
Changes in v7:
- fix memory leakage problem.
Changes in v8:
- fix iommu crash when use dual crtc.
- use frame start interrupt for vsync instead of line flag interrupt,
because the win config take affect at frame start time, if we use
ling flag interrupt, the address check often failed.
Adviced by Daniel Kurtz
- fix some bugs, mistake, remove unused function
- keep clock and vop disabled when probe end
- use drm_plane_helper_check_update to check update_plane if vaild
Changes in v9:
- fix suspend and resume bug, make iommu attach and detach safely.
- fix mail info style.
Changes in v10:
Adviced by Andrzej Hajda
- check drm_dev if it's NULL at PM suspend/resume
Adviced by Sean Paul
- use drm_fb_helper_prepare to init fb_helper funcs
- Optimized code structure and remove some unnecessary Variables.
Changes in v11:
- fix mistake that use wrong variable at rockchip sys_resume/sys_suspend
Changes in v12:
- fix compile problem with drm-next
- Optimization framebuffer reference/unreference
- Optimization Code structure
- fix pm suspend/resume problem
- fix vblank irq can't close problem
Changes in v13:
- fix vop compile warning.
Adviced by Daniel Vetter
- directly call rockchip_drm_load before register instead of
call ->load at the middle of drm register.
Changes in v14:
- revert v13 directly call rockchip_drm_load at bind, connector,
crtc _init should before dev-node kms object lookup idr and
conector sysfs need below minor node register, I don't like
split the connector init and register, so just call ->load at
the midile of drm register.
Changes in v15:
- remove depends on ARM_DMA_USE_IOMMU & IOMMU_API which cause
recursive dependency problem
- fix compile problems when build as a module.
Mark yao (3):
drm: rockchip: Add basic drm driver
dt-bindings: video: Add for rockchip display subsytem
dt-bindings: video: Add documentation for rockchip vop
.../devicetree/bindings/video/rockchip-drm.txt | 19 +
.../devicetree/bindings/video/rockchip-vop.txt | 58 +
drivers/gpu/drm/Kconfig | 2 +
drivers/gpu/drm/Makefile | 1 +
drivers/gpu/drm/rockchip/Kconfig | 17 +
drivers/gpu/drm/rockchip/Makefile | 8 +
drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 551 ++++++++
drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 68 +
drivers/gpu/drm/rockchip/rockchip_drm_fb.c | 201 +++
drivers/gpu/drm/rockchip/rockchip_drm_fb.h | 28 +
drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c | 210 +++
drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h | 21 +
drivers/gpu/drm/rockchip/rockchip_drm_gem.c | 294 ++++
drivers/gpu/drm/rockchip/rockchip_drm_gem.h | 54 +
drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 1455 ++++++++++++++++++++
drivers/gpu/drm/rockchip/rockchip_drm_vop.h | 201 +++
16 files changed, 3188 insertions(+)
--
1.7.9.5
^ permalink raw reply
* Re: Why not make kdbus use CUSE?
From: Richard Yao @ 2014-12-02 7:59 UTC (permalink / raw)
To: Greg Kroah-Hartman; +Cc: linux-kernel, linux-api
In-Reply-To: <20141202054850.GA17043@kroah.com>
[-- Attachment #1: Type: text/plain, Size: 5252 bytes --]
On 12/02/2014 12:48 AM, Greg Kroah-Hartman wrote:
> On Tue, Dec 02, 2014 at 12:40:09AM -0500, Richard Yao wrote:
>>>> They regard a userland compatibility shim in the systemd repostory to provide
>>>> backward compatibility for applications. Unfortunately, this is insufficient to
>>>> ensure compatibility because dependency trees have multiple levels. If cross
>>>> platform package A depends on cross platform library B, which depends on dbus,
>>>> and cross platform library B decides to switch to kdbus, then it ceases to be
>>>> cross platform and cross platform package A is now dependent on Linux kernels
>>>> with kdbus. Not only does that affect other POSIX systems, but it also affects
>>>> LTS versions of Linux.
>>>
>>> What does LTS versions have anything to do here? And what specific
>>> dependancies are you worried about?
>>
>> Lets say that you have a Linux 3.10 system and you want some package
>> that indirectly depends on the new API due to library dependencies. You
>> will have a problem. You could probably install an older version of the
>> library, but if the older version has a CVE, most end users will end up
>> between a rock and a hard place. This situation should merit some
>> consideration because you are taking something that lived previously in
>> userland, modifying it so that anything depending on the modifications
>> is no longer backward compatible and then tying it to new kernels.
>
> Then you need to get a better distro, as any "well run" long-term
> enterprise distro handles stuff like this for you. Otherwise you need
> to update systems properly. There's nothing that I can do here to help
> with that, nor do I ever want to, sorry.
Another option is to include KVM-style kernel compatibility code to
allow the module to be built against older kernels. If you target
2.6.32.y, 3.2.y, 3.4.y and 3.10.y, the risk of people on older Linux
systems being left behind would be minimized.
>> 1. Debugging kernel code is a pain while debugging user code is
>> relatively easy.
>
> You have full access to a debugger, what more do you need? :)
I would prefer not to start bringing userland daemons into the kernel
unless there is no other choice. That way, a wider range of people can
tackle bugs and the code could be applied to a larger number of systems.
> And why would you need to debug the kernel kdbus code? Is something not
> working properly in it? Otherwise just use wireshark to read the kdbus
> data stream and all should be fine.
Putting daemons in the kernel means that we further complicate already
complex relationships with regard to things like memory utilization and
CPU time. It is easier to deal with this in userland where we could
better utilize cgroups.
>> 2. Security vulnerabilities in kernel code give complete access to
>> everything while security vulnerabilities in userspace code can be
>> limited in scope by SELinux.
>
> Kernel code is hard, security matters, yes I know this, we all have been
> doing this for a very long time. Of course bugs happen, but if you look
> closely, your "attack surface" is now smaller using kdbus than it was
> using old-style dbus.
Lets say that I have a system running LXC containers, someone does full
disclosure of proof of concept code for an arbitrary code execution zero
day and then someone else tries the exploit in a LXC container on
mysystem. With old-style dbus, only the container is affected and if
selinux is used, then it is possible to restrict daemon to things in the
container using dbus. A FUSE daemon using the new protocol is similar.
However, an in-kernel version not only means that the attacker breaks
out, but he has the ability to execute code with full kernel privileges.
Every Linux container on the system is therefore compromised.
I heard quite clearly at LinuxCon Europe that there are no expected
benefits from using the shim with kdbus such that we have the equivalent
of the original dbus daemon in the kernel, but there were plenty of
benefits from the protocol. If that is the case, it seems that being in
the kernel is not a necessity, but the new protocol is. FUSE might be
somewhat slower than an in-kernel filesystem, but it allows us to
enforce least privilege like we can do now with the current dbus daemon.
We cannot do that with kdbus/kdbusfs. If the reduction in context switch
overhead actually mattered, I would understand the desire to put this
into the kernel, but I have heard quite consistently that the context
switch overhead is not a significant motivation for pushing this code
into the kernel. If it were, the current userland code could have been
adapted into a kernel module.
>> 3. Integration with things like LXC should be easier from userspace,
>> where each container can have its own daemon.
>
> How does the current implementation not work properly for this? The
> filesystem implementation makes this easier than ever, while sticking
> with the character device made this quite difficult in different ways.
As you pointed out, my information was out of date. Making this into a
filesystem is an excellent idea that handles container integration quite
nicely.
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 884 bytes --]
^ permalink raw reply
* Re: [PATCH] media: platform: add VPFE capture driver support for AM437X
From: Prabhakar Lad @ 2014-12-02 7:49 UTC (permalink / raw)
To: Hans Verkuil
Cc: LMML, devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, LKML,
linux-api, Hans Verkuil
In-Reply-To: <547D6B07.6050803-qWit8jRvyhVmR6Xm/wNWPw@public.gmane.org>
Hi Hans,
On Tue, Dec 2, 2014 at 7:32 AM, Hans Verkuil <hverkuil-qWit8jRvyhVmR6Xm/wNWPw@public.gmane.org> wrote:
> On 12/01/2014 11:27 PM, Prabhakar Lad wrote:
>> Hi Hans,
>>
>> On Mon, Dec 1, 2014 at 11:00 AM, Hans Verkuil <hverkuil-qWit8jRvyhVmR6Xm/wNWPw@public.gmane.org> wrote:
>>> On 12/01/2014 11:11 AM, Hans Verkuil wrote:
>>>> Hi all,
>>>>
>>>> Thanks for the patch, review comments are below.
>>>>
>>>> For the next version I would appreciate if someone can test this driver with
>>>> the latest v4l2-compliance from the v4l-utils git repo. If possible test streaming
>>>> as well (v4l2-compliance -s), ideally with both a sensor and a STD receiver input.
>>>> But that depends on the available hardware of course.
>>>>
>>>> I'd like to see the v4l2-compliance output. It's always good to have that on
>>>> record.
>>>>
>>>>
>>>> On 11/24/2014 02:10 AM, Lad, Prabhakar wrote:
>>>>> From: Benoit Parrot <bparrot-l0cyMroinI0@public.gmane.org>
>>>>>
>>>>> This patch adds Video Processing Front End (VPFE) driver for
>>>>> AM437X family of devices
>>>>> Driver supports the following:
>>>>> - V4L2 API using MMAP buffer access based on videobuf2 api
>>>>> - Asynchronous sensor/decoder sub device registration
>>>>> - DT support
>>>>>
>>>>> Signed-off-by: Benoit Parrot <bparrot-l0cyMroinI0@public.gmane.org>
>>>>> Signed-off-by: Darren Etheridge <detheridge-l0cyMroinI0@public.gmane.org>
>>>>> Signed-off-by: Lad, Prabhakar <prabhakar.csengg-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
>>>>> ---
>>>>> .../devicetree/bindings/media/ti-am437xx-vpfe.txt | 61 +
>>>>> MAINTAINERS | 9 +
>>>>> drivers/media/platform/Kconfig | 1 +
>>>>> drivers/media/platform/Makefile | 2 +
>>>>> drivers/media/platform/am437x/Kconfig | 10 +
>>>>> drivers/media/platform/am437x/Makefile | 2 +
>>>>> drivers/media/platform/am437x/vpfe.c | 2764 ++++++++++++++++++++
>>>>> drivers/media/platform/am437x/vpfe.h | 286 ++
>>>>> drivers/media/platform/am437x/vpfe_regs.h | 140 +
>>>>> include/uapi/linux/Kbuild | 1 +
>>>>> include/uapi/linux/am437x_vpfe.h | 122 +
>>>>> 11 files changed, 3398 insertions(+)
>>>
>>> Can the source names be improved? There are too many 'vpfe' sources.
>>> Perhaps prefix all with 'am437x-'?
>>>
>> I did think of it but, dropped it as anyway the source's are present
>> in am437x folder, again naming the files am437x-xxx.x didn't make
>> me feel good. If you still insists I'll do it.
>
> Yes, please do. If you look at most other drivers they all have a prefix.
>
> Another reason is that the media_build system expects unique names.
>
OK makes sense I'll prefix it with 'am437x-'. probably fixing all the review
comments i'll post a v2 end of the day.
Thanks,
--Prabhakar Lad
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH] media: platform: add VPFE capture driver support for AM437X
From: Prabhakar Lad @ 2014-12-02 7:47 UTC (permalink / raw)
To: Hans Verkuil
Cc: LMML, devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, LKML,
linux-api, Hans Verkuil
In-Reply-To: <547D69C2.20503-qWit8jRvyhVmR6Xm/wNWPw@public.gmane.org>
Hi Hans,
On Tue, Dec 2, 2014 at 7:26 AM, Hans Verkuil <hverkuil-qWit8jRvyhVmR6Xm/wNWPw@public.gmane.org> wrote:
> On 12/01/2014 11:17 PM, Prabhakar Lad wrote:
>> Hi Hans,
>>
>> Thanks for the review.
>>
>> On Mon, Dec 1, 2014 at 10:11 AM, Hans Verkuil <hverkuil-qWit8jRvyhVmR6Xm/wNWPw@public.gmane.org> wrote:
>>> Hi all,
>>>
>>> Thanks for the patch, review comments are below.
>>>
>>> For the next version I would appreciate if someone can test this driver with
>>> the latest v4l2-compliance from the v4l-utils git repo. If possible test streaming
>>> as well (v4l2-compliance -s), ideally with both a sensor and a STD receiver input.
>>> But that depends on the available hardware of course.
>>>
>>> I'd like to see the v4l2-compliance output. It's always good to have that on
>>> record.
>>>
>> Following is the compliance output, missed it post it along with patch:
>>
>> root@am437x-evm:~# ./v4l2-compliance -s -i 0 -v
>> Driver Info:
>> Driver name : vpfe
>> Card type :[ 70.363908] vpfe 48326000.vpfe:
>> ================= START STATUS =================
>> TI AM437x VPFE
>> Bus info : platform:vpfe [ 70.375576] vpfe
>> 48326000.vpfe: ================== END STATUS ==================
>> 48326000.vpfe
>> Driver version: 3.18.0
>> Capabil[ 70.388485] vpfe 48326000.vpfe: invalid input index: 1
>> ities : 0x85200001
>> Video Capture
>> Read/Write
>> Streaming
>> Extended Pix Format
>> Device Capabilities
>> Device Caps : 0x05200001
>> Video Capture
>> Read/Write
>> Streaming
>> Extended Pix Format
>>
>> Compliance test for device /dev/video0 (not using libv4l2):
>>
>> Required ioctls:
>> test VIDIOC_QUERYCAP: OK
>>
>> Allow for multiple opens:
>> test second video open: OK
>> test VIDIOC_QUERYCAP: OK
>> test VIDIOC_G/S_PRIORITY: OK
>>
>> Debug ioctls:
>> test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
>> test VIDIOC_LOG_STATUS: OK
>>
>> Input ioctls:
>> test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
>> test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
>> test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
>> test VIDIOC_ENUMAUDIO: OK (Not Supported)
>> test VIDIOC_G/S/ENUMINPUT: OK
>> test VIDIOC_G/S_AUDIO: OK (Not Supported)
>> Inputs: 1 Audio Inputs: 0 Tuners: 0
>>
>> Output ioctls:
>> test VIDIOC_G/S_MODULATOR: OK (Not Supported)
>> test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
>> test VIDIOC_ENUMAUDOUT: OK (Not Supported)
>> test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
>> test VIDIOC_G/S_AUDOUT: OK (Not Supported)
>> Outputs: 0 Audio Outputs: 0 Modulators: 0
>>
>> Input/Output configuration ioctls:
>> test VIDIOC_ENUM/G/S/QUERY_STD: OK
>> test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
>> test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
>> test VIDIOC_G/S_EDID: OK (Not Supported)
>>
>> Test input 0:
>>
>> Control ioctls:
>> test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
>> test VIDIOC_QUERYCTRL: OK (Not Supported)
>> test VIDIOC_G/S_CTRL: OK (Not Supported)
>> test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
>> test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
>> test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
>> Standard Controls: 0 Private Controls: 0
>>
>> Format ioctls:
>> info: found 7 framesizes for pixel format 56595559
>> info: found 7 framesizes for pixel format 59565955
>> info: found 7 framesizes for pixel format 52424752
>> info: found 7 framesizes for pixel format 31384142
>> info: found 4 formats for buftype 1
>> test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
>> test VIDIOC_G/S_PARM: OK
>> test VIDIOC_G_FBUF: OK (Not Supported)
>> test VIDIOC_G_FMT: OK
>> test VIDIOC_TRY_FMT: OK
>> info: Global format check succeeded for type 1
>> test VIDIOC_S_FMT: OK
>> test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
>>
>> Codec ioctls:
>> test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
>> test VIDIOC_G_ENC_INDEX: OK (Not Supported)
>> test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)
>>
>> Buffer ioctls:
>> info: test buftype Video Capture
>> test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
>> test VIDIOC_EXPBUF: OK
>>
>> Streaming ioctls:
>> test read/write: OK
>> Video Capture:
>> Buffer: 0 Sequence: 0 Field: None Timestamp: 74.956437s
>> Buffer: 1 Sequence: 0 Field: None Timestamp: 74.979310s
>> Buffer: 2 Sequence: 0 Field: None Timestamp: 75.002191s
>> Buffer: 3 Sequence: 0 Field: None Timestamp: 75.208114s
>> Buffer: 0 Sequence: 0 Field: None Timestamp: 75.230998s
>
> Hmm, sequence is always 0. Is the sequence field set? And why doesn't
> v4l2-compliance fail on this?
>
>> Buffer: 1 Sequence: 0 Field: None Timestamp: 75.253877s
>
> <snip>
>
>> test MMAP: OK
>> test USERPTR: OK (Not Supported)
>> test DMABUF: Cannot test, specify --expbuf-device
>>
>> Total: 42, Succeeded: 42, Failed: 0, Warnings: 0
>>>
>> [Snip]
>
>>>> +static int vpfe_enum_fmt(struct file *file, void *priv,
>>>> + struct v4l2_fmtdesc *f)
>>>> +{
>>>> + struct vpfe_device *vpfe = video_drvdata(file);
>>>> + struct v4l2_subdev_mbus_code_enum mbus_code;
>>>> + struct vpfe_subdev_info *sdinfo;
>>>> + struct vpfe_fmt *fmt;
>>>> + int ret;
>>>> +
>>>> + vpfe_dbg(2, vpfe, "vpfe_enum_format index:%d\n",
>>>> + f->index);
>>>> +
>>>> + sdinfo = vpfe->current_subdev;
>>>> + if (!sdinfo->sd)
>>>> + return -EINVAL;
>>>> +
>>>> + mbus_code.index = f->index;
>>>> + ret = v4l2_subdev_call(sdinfo->sd, pad, enum_mbus_code,
>>>> + NULL, &mbus_code);
>>>> + if (ret)
>>>> + return -EINVAL;
>>>> +
>>>> + /* convert mbus_format to v4l2_format */
>>>> + fmt = find_format_by_code(mbus_code.code);
>>>> + if (!fmt) {
>>>> + vpfe_err(vpfe, "mbus code 0x%08x not found\n",
>>>> + mbus_code.code);
>>>> + return -EINVAL;
>>>> + }
>>>
>>> Hmm. If a subdev supports more media bus codes then are supported by this
>>> driver, then the enumeration will stop as soon as such an unsupported code
>>> is found.
>>>
>>> What you want to do here is enumerate over the pixelformats that are supported
>>> by both this driver and the subdev. It is probably something you want to
>>> determine at the time the subdev is loaded and just mark unsupported formats
>>> at that time so that they can be skipped here.
>>>
>> So probably populate the supported pixelformats in s_input call ,
>> by calling enm_mbus_code ?
>
> I would do this during driver initialization, it's a one time thing that can
> be done there.
>
OK will do it when subdev is registered.
Thanks,
--Prabhakar Lad
^ permalink raw reply
* Re: [PATCH] media: platform: add VPFE capture driver support for AM437X
From: Hans Verkuil @ 2014-12-02 7:32 UTC (permalink / raw)
To: Prabhakar Lad
Cc: LMML, devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, LKML,
linux-api, Hans Verkuil
In-Reply-To: <CA+V-a8uL4J0Y2ZfDxe7jhxzGcCNe9QbCDUjDZrC+mZ5E6sX2jg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
On 12/01/2014 11:27 PM, Prabhakar Lad wrote:
> Hi Hans,
>
> On Mon, Dec 1, 2014 at 11:00 AM, Hans Verkuil <hverkuil-qWit8jRvyhVmR6Xm/wNWPw@public.gmane.org> wrote:
>> On 12/01/2014 11:11 AM, Hans Verkuil wrote:
>>> Hi all,
>>>
>>> Thanks for the patch, review comments are below.
>>>
>>> For the next version I would appreciate if someone can test this driver with
>>> the latest v4l2-compliance from the v4l-utils git repo. If possible test streaming
>>> as well (v4l2-compliance -s), ideally with both a sensor and a STD receiver input.
>>> But that depends on the available hardware of course.
>>>
>>> I'd like to see the v4l2-compliance output. It's always good to have that on
>>> record.
>>>
>>>
>>> On 11/24/2014 02:10 AM, Lad, Prabhakar wrote:
>>>> From: Benoit Parrot <bparrot-l0cyMroinI0@public.gmane.org>
>>>>
>>>> This patch adds Video Processing Front End (VPFE) driver for
>>>> AM437X family of devices
>>>> Driver supports the following:
>>>> - V4L2 API using MMAP buffer access based on videobuf2 api
>>>> - Asynchronous sensor/decoder sub device registration
>>>> - DT support
>>>>
>>>> Signed-off-by: Benoit Parrot <bparrot-l0cyMroinI0@public.gmane.org>
>>>> Signed-off-by: Darren Etheridge <detheridge-l0cyMroinI0@public.gmane.org>
>>>> Signed-off-by: Lad, Prabhakar <prabhakar.csengg-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
>>>> ---
>>>> .../devicetree/bindings/media/ti-am437xx-vpfe.txt | 61 +
>>>> MAINTAINERS | 9 +
>>>> drivers/media/platform/Kconfig | 1 +
>>>> drivers/media/platform/Makefile | 2 +
>>>> drivers/media/platform/am437x/Kconfig | 10 +
>>>> drivers/media/platform/am437x/Makefile | 2 +
>>>> drivers/media/platform/am437x/vpfe.c | 2764 ++++++++++++++++++++
>>>> drivers/media/platform/am437x/vpfe.h | 286 ++
>>>> drivers/media/platform/am437x/vpfe_regs.h | 140 +
>>>> include/uapi/linux/Kbuild | 1 +
>>>> include/uapi/linux/am437x_vpfe.h | 122 +
>>>> 11 files changed, 3398 insertions(+)
>>
>> Can the source names be improved? There are too many 'vpfe' sources.
>> Perhaps prefix all with 'am437x-'?
>>
> I did think of it but, dropped it as anyway the source's are present
> in am437x folder, again naming the files am437x-xxx.x didn't make
> me feel good. If you still insists I'll do it.
Yes, please do. If you look at most other drivers they all have a prefix.
Another reason is that the media_build system expects unique names.
Regards,
Hans
>> In general I prefer '-' over '_' in a source name: it's looks better
>> IMHO.
>>
> I am almost done with all the review comments, if we take a decision
> on this quickly I can post a v2 soon.
>
>> One question, unrelated to this patch series:
>>
>> Prabhakar, would it make sense to look at the various existing TI sources
>> as well and rename them to make it more explicit for which SoCs they are
>> meant? Most are pretty vague with variations on vpe, vpif, vpfe, etc. but
>> no reference to the actual SoC they are for.
>>
> vpe - definitely needs to be changed.
> vpif - under davinci is common for (Davinci series
> dm6467/dm6467t/omapl138/am1808)
> vpfe - under davinci is common for (Davinci series dm36x/dm6446/dm355)
>
> I am falling short of names for renaming this common drivers :)
>
> Thanks,
> --Prabhakar Lad
> --
> To unsubscribe from this list: send the line "unsubscribe linux-media" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox