| /* -*- mode: c; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ |
| /* vi: set ts=8 sw=8 sts=8: */ |
| /*************************************************************************/ /*! |
| @File |
| @Codingstyle LinuxKernel |
| @Copyright Copyright (c) Imagination Technologies Ltd. All Rights Reserved |
| @License MIT |
| |
| The contents of this file are subject to the MIT license as set out below. |
| |
| Permission is hereby granted, free of charge, to any person obtaining a copy |
| of this software and associated documentation files (the "Software"), to deal |
| in the Software without restriction, including without limitation the rights |
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| copies of the Software, and to permit persons to whom the Software is |
| furnished to do so, subject to the following conditions: |
| |
| The above copyright notice and this permission notice shall be included in |
| all copies or substantial portions of the Software. |
| |
| This License is also included in this distribution in the file called |
| "MIT-COPYING". |
| |
| EXCEPT AS OTHERWISE STATED IN A NEGOTIATED AGREEMENT: (A) THE SOFTWARE IS |
| PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING |
| BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR |
| PURPOSE AND NONINFRINGEMENT; AND (B) IN NO EVENT SHALL THE AUTHORS OR |
| COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER |
| IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| */ /**************************************************************************/ |
| |
| #include <linux/version.h> |
| #include <drm/drmP.h> |
| #include <drm/drm_crtc.h> |
| #include <linux/mutex.h> |
| #include <linux/list.h> |
| #include <linux/random.h> |
| #include <linux/slab.h> |
| #include <linux/atomic.h> |
| #include <linux/types.h> |
| #include <net/netlink.h> |
| #include <net/genetlink.h> |
| #include <linux/bug.h> |
| |
| #include "drm_nulldisp_drv.h" |
| #include "drm_nulldisp_netlink.h" |
| #include "kernel_compatibility.h" |
| |
| #include "netlink.h" |
| |
| struct nlpvrdpy { |
| atomic_t connected; |
| struct net *net; |
| u32 dst_portid; |
| struct drm_device *dev; |
| nlpvrdpy_disconnect_cb disconnect_cb; |
| void *disconnect_cb_data; |
| nlpvrdpy_flipped_cb flipped_cb; |
| void *flipped_cb_data; |
| nlpvrdpy_copied_cb copied_cb; |
| void *copied_cb_data; |
| struct mutex mutex; |
| struct list_head nl_list; |
| }; |
| #define NLPVRDPY_MINOR(nlpvrdpy) ((unsigned)((nlpvrdpy)->dev->primary->index)) |
| |
| /* Command internal flags */ |
| #define NLPVRDPY_CIF_NLPVRDPY_NOT_CONNECTED 0x00000001 |
| #define NLPVRDPY_CIF_NLPVRDPY 0x00000002 |
| |
| static LIST_HEAD(nlpvrdpy_list); |
| static DEFINE_MUTEX(nlpvrdpy_list_mutex); |
| |
| static inline void nlpvrdpy_lock(struct nlpvrdpy *nlpvrdpy) |
| { |
| mutex_lock(&nlpvrdpy->mutex); |
| } |
| |
| static inline void nlpvrdpy_unlock(struct nlpvrdpy *nlpvrdpy) |
| { |
| mutex_unlock(&nlpvrdpy->mutex); |
| } |
| |
| struct nlpvrdpy *nlpvrdpy_create(struct drm_device *dev, |
| nlpvrdpy_disconnect_cb disconnect_cb, |
| void *disconnect_cb_data, |
| nlpvrdpy_flipped_cb flipped_cb, |
| void *flipped_cb_data, |
| nlpvrdpy_copied_cb copied_cb, |
| void *copied_cb_data) |
| { |
| struct nlpvrdpy *nlpvrdpy = kzalloc(sizeof(*nlpvrdpy), GFP_KERNEL); |
| |
| if (!nlpvrdpy) |
| return NULL; |
| |
| mutex_init(&nlpvrdpy->mutex); |
| INIT_LIST_HEAD(&nlpvrdpy->nl_list); |
| |
| atomic_set(&nlpvrdpy->connected, 0); |
| |
| nlpvrdpy->dev = dev; |
| nlpvrdpy->disconnect_cb = disconnect_cb; |
| nlpvrdpy->disconnect_cb_data = disconnect_cb_data; |
| nlpvrdpy->flipped_cb = flipped_cb; |
| nlpvrdpy->flipped_cb_data = flipped_cb_data; |
| nlpvrdpy->copied_cb = copied_cb; |
| nlpvrdpy->copied_cb_data = copied_cb_data; |
| |
| mutex_lock(&nlpvrdpy_list_mutex); |
| list_add_tail(&nlpvrdpy->nl_list, &nlpvrdpy_list); |
| mutex_unlock(&nlpvrdpy_list_mutex); |
| |
| return nlpvrdpy; |
| } |
| |
| void nlpvrdpy_destroy(struct nlpvrdpy *nlpvrdpy) |
| { |
| if (!nlpvrdpy) |
| return; |
| |
| mutex_lock(&nlpvrdpy_list_mutex); |
| nlpvrdpy_lock(nlpvrdpy); |
| list_del(&nlpvrdpy->nl_list); |
| nlpvrdpy_unlock(nlpvrdpy); |
| mutex_unlock(&nlpvrdpy_list_mutex); |
| |
| mutex_destroy(&nlpvrdpy->mutex); |
| |
| kfree(nlpvrdpy); |
| } |
| |
| static struct nlpvrdpy *nlpvrdpy_lookup(u32 minor) |
| { |
| struct nlpvrdpy *nlpvrdpy = NULL; |
| struct nlpvrdpy *iter; |
| |
| mutex_lock(&nlpvrdpy_list_mutex); |
| list_for_each_entry(iter, &nlpvrdpy_list, nl_list) { |
| if (NLPVRDPY_MINOR(iter) == minor) { |
| nlpvrdpy = iter; |
| nlpvrdpy_lock(nlpvrdpy); |
| break; |
| } |
| } |
| mutex_unlock(&nlpvrdpy_list_mutex); |
| |
| return nlpvrdpy; |
| } |
| |
| static int nlpvrdpy_pre_cmd(const struct genl_ops *ops, |
| struct sk_buff *skb, |
| struct genl_info *info) |
| { |
| struct nlattr **attrs = info->attrs; |
| struct nlpvrdpy *nlpvrdpy = NULL; |
| int ret; |
| |
| if (ops->internal_flags & NLPVRDPY_CIF_NLPVRDPY_NOT_CONNECTED) { |
| if (!(ops->flags & GENL_ADMIN_PERM)) |
| return -EINVAL; |
| } |
| |
| if (ops->internal_flags & (NLPVRDPY_CIF_NLPVRDPY_NOT_CONNECTED | |
| NLPVRDPY_CIF_NLPVRDPY)) { |
| u32 minor; |
| |
| if (!attrs[NLPVRDPY_ATTR_MINOR]) |
| return -EINVAL; |
| |
| minor = nla_get_u32(attrs[NLPVRDPY_ATTR_MINOR]); |
| |
| nlpvrdpy = nlpvrdpy_lookup(minor); |
| if (!nlpvrdpy) |
| return -ENODEV; |
| |
| if (ops->internal_flags & NLPVRDPY_CIF_NLPVRDPY) { |
| if (!atomic_read(&nlpvrdpy->connected)) { |
| ret = -ENOTCONN; |
| goto err_unlock; |
| } |
| if ((nlpvrdpy->net != genl_info_net(info)) || |
| (nlpvrdpy->dst_portid != info->snd_portid)) { |
| ret = -EPROTO; |
| goto err_unlock; |
| } |
| } |
| |
| info->user_ptr[0] = nlpvrdpy; |
| } |
| |
| ret = 0; |
| |
| err_unlock: |
| nlpvrdpy_unlock(nlpvrdpy); |
| return ret; |
| } |
| |
| static void nlpvrdpy_post_cmd(const struct genl_ops *ops, |
| struct sk_buff *skb, |
| struct genl_info *info) |
| { |
| } |
| |
| static struct genl_family nlpvrdpy_family = { |
| .name = "nlpvrdpy", |
| .version = 1, |
| .maxattr = NLPVRDPY_ATTR_MAX, |
| .pre_doit = &nlpvrdpy_pre_cmd, |
| .post_doit = &nlpvrdpy_post_cmd |
| }; |
| |
| /* Must be called with the struct nlpvrdpy mutex held */ |
| static int nlpvrdpy_send_msg_locked(struct nlpvrdpy *nlpvrdpy, |
| struct sk_buff *msg) |
| { |
| int err; |
| |
| if (atomic_read(&nlpvrdpy->connected)) { |
| err = genlmsg_unicast(nlpvrdpy->net, msg, nlpvrdpy->dst_portid); |
| if (err == -ECONNREFUSED) |
| atomic_set(&nlpvrdpy->connected, 0); |
| } else { |
| err = -ENOTCONN; |
| nlmsg_free(msg); |
| } |
| |
| return err; |
| } |
| |
| static int nlpvrdpy_send_msg(struct nlpvrdpy *nlpvrdpy, struct sk_buff *msg) |
| { |
| int err; |
| |
| nlpvrdpy_lock(nlpvrdpy); |
| err = nlpvrdpy_send_msg_locked(nlpvrdpy, msg); |
| nlpvrdpy_unlock(nlpvrdpy); |
| |
| return err; |
| } |
| |
| void nlpvrdpy_send_disconnect(struct nlpvrdpy *nlpvrdpy) |
| { |
| struct sk_buff *msg; |
| void *hdr; |
| int err; |
| |
| if (!atomic_read(&nlpvrdpy->connected)) |
| return; |
| |
| msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); |
| if (!msg) |
| return; |
| |
| hdr = genlmsg_put(msg, nlpvrdpy->dst_portid, 0, |
| &nlpvrdpy_family, 0, NLPVRDPY_CMD_DISCONNECT); |
| if (!hdr) |
| goto err_msg_free; |
| |
| err = nla_put_u32(msg, NLPVRDPY_ATTR_MINOR, NLPVRDPY_MINOR(nlpvrdpy)); |
| if (err) |
| goto err_msg_free; |
| |
| genlmsg_end(msg, hdr); |
| |
| nlpvrdpy_lock(nlpvrdpy); |
| |
| (void) nlpvrdpy_send_msg_locked(nlpvrdpy, msg); |
| |
| atomic_set(&nlpvrdpy->connected, 0); |
| nlpvrdpy->net = NULL; |
| nlpvrdpy->dst_portid = 0; |
| |
| nlpvrdpy_unlock(nlpvrdpy); |
| |
| return; |
| |
| err_msg_free: |
| nlmsg_free(msg); |
| } |
| |
| static int nlpvrdpy_put_fb_attributes(struct sk_buff *msg, |
| struct drm_framebuffer *fb, |
| struct nlpvrdpy *nlpvrdpy, |
| u64 *plane_addr, |
| u64 *plane_size) |
| { |
| #define RETURN_ON_ERROR(f) \ |
| { \ |
| int err = (f); \ |
| if (err) { \ |
| pr_err("%s: command failed: %s", __func__, #f); \ |
| return err; \ |
| } \ |
| } |
| |
| const int num_planes = nulldisp_drm_fb_num_planes(fb); |
| |
| RETURN_ON_ERROR(nla_put_u32(msg, NLPVRDPY_ATTR_MINOR, NLPVRDPY_MINOR(nlpvrdpy))); |
| |
| RETURN_ON_ERROR(nla_put_u8(msg, NLPVRDPY_ATTR_NUM_PLANES, num_planes)); |
| |
| RETURN_ON_ERROR(nla_put_u32(msg, NLPVRDPY_ATTR_WIDTH, fb->width)); |
| RETURN_ON_ERROR(nla_put_u32(msg, NLPVRDPY_ATTR_HEIGHT, fb->height)); |
| RETURN_ON_ERROR(nla_put_u32(msg, NLPVRDPY_ATTR_PIXFMT, nulldisp_drm_fb_format(fb))); |
| RETURN_ON_ERROR(nla_put_u64_64bit(msg, |
| NLPVRDPY_ATTR_FB_MODIFIER, |
| nulldisp_drm_fb_modifier(fb), |
| NLPVRDPY_ATTR_PAD)); |
| |
| /* |
| * TODO YUV: get the actual CSC and BPP |
| * for now only 8-bit BT601 short range is supported |
| */ |
| RETURN_ON_ERROR(nla_put_u8(msg, NLPVRDPY_ATTR_YUV_CSC, 1)); /* IMG_COLORSPACE_BT601_CONFORMANT_RANGE */ |
| RETURN_ON_ERROR(nla_put_u8(msg, NLPVRDPY_ATTR_YUV_BPP, 8)); /* 8-bit per sample */ |
| |
| RETURN_ON_ERROR(nla_put_u64_64bit(msg, NLPVRDPY_ATTR_PLANE0_ADDR, plane_addr[0], NLPVRDPY_ATTR_PAD)); |
| RETURN_ON_ERROR(nla_put_u64_64bit(msg, NLPVRDPY_ATTR_PLANE0_SIZE, plane_size[0], NLPVRDPY_ATTR_PAD)); |
| RETURN_ON_ERROR(nla_put_u64_64bit(msg, NLPVRDPY_ATTR_PLANE0_OFFSET, fb->offsets[0], NLPVRDPY_ATTR_PAD)); |
| RETURN_ON_ERROR(nla_put_u64_64bit(msg, NLPVRDPY_ATTR_PLANE0_PITCH, fb->pitches[0], NLPVRDPY_ATTR_PAD)); |
| |
| if (num_planes > 1) { |
| RETURN_ON_ERROR(nla_put_u64_64bit(msg, NLPVRDPY_ATTR_PLANE1_ADDR, plane_addr[1], NLPVRDPY_ATTR_PAD)); |
| RETURN_ON_ERROR(nla_put_u64_64bit(msg, NLPVRDPY_ATTR_PLANE1_SIZE, plane_size[1], NLPVRDPY_ATTR_PAD)); |
| RETURN_ON_ERROR(nla_put_u64_64bit(msg, NLPVRDPY_ATTR_PLANE1_OFFSET, fb->offsets[1], NLPVRDPY_ATTR_PAD)); |
| RETURN_ON_ERROR(nla_put_u64_64bit(msg, NLPVRDPY_ATTR_PLANE1_PITCH, fb->pitches[1], NLPVRDPY_ATTR_PAD)); |
| } |
| |
| if (num_planes > 2) { |
| RETURN_ON_ERROR(nla_put_u64_64bit(msg, NLPVRDPY_ATTR_PLANE2_ADDR, plane_addr[2], NLPVRDPY_ATTR_PAD)); |
| RETURN_ON_ERROR(nla_put_u64_64bit(msg, NLPVRDPY_ATTR_PLANE2_SIZE, plane_size[2], NLPVRDPY_ATTR_PAD)); |
| RETURN_ON_ERROR(nla_put_u64_64bit(msg, NLPVRDPY_ATTR_PLANE2_OFFSET, fb->offsets[2], NLPVRDPY_ATTR_PAD)); |
| RETURN_ON_ERROR(nla_put_u64_64bit(msg, NLPVRDPY_ATTR_PLANE2_PITCH, fb->pitches[2], NLPVRDPY_ATTR_PAD)); |
| } |
| |
| WARN_ON_ONCE(num_planes > NLPVRDPY_MAX_NUM_PLANES); |
| |
| return 0; |
| #undef RETURN_ON_ERROR |
| } |
| |
| int nlpvrdpy_send_flip(struct nlpvrdpy *nlpvrdpy, |
| struct drm_framebuffer *fb, |
| u64 *plane_addr, |
| u64 *plane_size) |
| { |
| struct sk_buff *msg; |
| void *hdr; |
| int err; |
| |
| if (!atomic_read(&nlpvrdpy->connected)) |
| return -ENOTCONN; |
| |
| msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); |
| if (!msg) |
| return -ENOMEM; |
| |
| hdr = genlmsg_put(msg, nlpvrdpy->dst_portid, 0, |
| &nlpvrdpy_family, 0, NLPVRDPY_CMD_FLIP); |
| if (!hdr) { |
| err = -ENOMEM; |
| goto err_msg_free; |
| } |
| |
| err = nlpvrdpy_put_fb_attributes(msg, fb, nlpvrdpy, plane_addr, |
| plane_size); |
| if (err) |
| goto err_msg_free; |
| |
| genlmsg_end(msg, hdr); |
| |
| return nlpvrdpy_send_msg(nlpvrdpy, msg); |
| |
| err_msg_free: |
| nlmsg_free(msg); |
| return err; |
| } |
| |
| int nlpvrdpy_send_copy(struct nlpvrdpy *nlpvrdpy, |
| struct drm_framebuffer *fb, |
| u64 *plane_addr, |
| u64 *plane_size) |
| { |
| struct sk_buff *msg; |
| void *hdr; |
| int err; |
| |
| if (!atomic_read(&nlpvrdpy->connected)) |
| return -ENOTCONN; |
| |
| msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); |
| if (!msg) |
| return -ENOMEM; |
| |
| hdr = genlmsg_put(msg, nlpvrdpy->dst_portid, 0, |
| &nlpvrdpy_family, 0, NLPVRDPY_CMD_COPY); |
| if (!hdr) { |
| err = -ENOMEM; |
| goto err_msg_free; |
| } |
| |
| err = nlpvrdpy_put_fb_attributes(msg, fb, nlpvrdpy, plane_addr, |
| plane_size); |
| if (err) |
| goto err_msg_free; |
| |
| genlmsg_end(msg, hdr); |
| |
| return nlpvrdpy_send_msg(nlpvrdpy, msg); |
| |
| err_msg_free: |
| nlmsg_free(msg); |
| return err; |
| } |
| |
| static int nlpvrdpy_cmd_connect(struct sk_buff *skb, struct genl_info *info) |
| { |
| struct nlpvrdpy *nlpvrdpy = info->user_ptr[0]; |
| struct sk_buff *msg; |
| void *hdr; |
| int err; |
| |
| msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); |
| if (!msg) |
| return -ENOMEM; |
| |
| hdr = genlmsg_put_reply(msg, info, &nlpvrdpy_family, |
| 0, NLPVRDPY_CMD_CONNECTED); |
| if (!hdr) { |
| err = -ENOMEM; |
| goto err_msg_free; |
| } |
| |
| err = nla_put_string(msg, NLPVRDPY_ATTR_NAME, |
| nlpvrdpy->dev->driver->name); |
| if (err) |
| goto err_msg_free; |
| |
| genlmsg_end(msg, hdr); |
| |
| err = genlmsg_reply(msg, info); |
| |
| if (!err) { |
| nlpvrdpy_lock(nlpvrdpy); |
| |
| nlpvrdpy->net = genl_info_net(info); |
| nlpvrdpy->dst_portid = info->snd_portid; |
| atomic_set(&nlpvrdpy->connected, 1); |
| |
| nlpvrdpy_unlock(nlpvrdpy); |
| } |
| |
| return err; |
| |
| err_msg_free: |
| nlmsg_free(msg); |
| return err; |
| } |
| |
| static int nlpvrdpy_cmd_disconnect(struct sk_buff *skb, struct genl_info *info) |
| { |
| struct nlpvrdpy *nlpvrdpy = info->user_ptr[0]; |
| |
| atomic_set(&nlpvrdpy->connected, 0); |
| |
| if (nlpvrdpy->disconnect_cb) |
| nlpvrdpy->disconnect_cb(nlpvrdpy->disconnect_cb_data); |
| |
| return 0; |
| } |
| |
| static int nlpvrdpy_cmd_flipped(struct sk_buff *skb, struct genl_info *info) |
| { |
| struct nlpvrdpy *nlpvrdpy = info->user_ptr[0]; |
| |
| return (nlpvrdpy->flipped_cb) ? |
| nlpvrdpy->flipped_cb(nlpvrdpy->flipped_cb_data) : |
| 0; |
| } |
| |
| static int nlpvrdpy_cmd_copied(struct sk_buff *skb, struct genl_info *info) |
| { |
| struct nlpvrdpy *nlpvrdpy = info->user_ptr[0]; |
| |
| return (nlpvrdpy->copied_cb) ? |
| nlpvrdpy->copied_cb(nlpvrdpy->copied_cb_data) : |
| 0; |
| } |
| |
| static struct genl_ops nlpvrdpy_ops[] = { |
| { |
| .cmd = NLPVRDPY_CMD_CONNECT, |
| .policy = nlpvrdpy_policy, |
| .doit = nlpvrdpy_cmd_connect, |
| .flags = GENL_ADMIN_PERM, |
| .internal_flags = NLPVRDPY_CIF_NLPVRDPY_NOT_CONNECTED |
| }, |
| { |
| .cmd = NLPVRDPY_CMD_DISCONNECT, |
| .policy = nlpvrdpy_policy, |
| .doit = nlpvrdpy_cmd_disconnect, |
| .flags = 0, |
| .internal_flags = NLPVRDPY_CIF_NLPVRDPY |
| }, |
| { |
| .cmd = NLPVRDPY_CMD_FLIPPED, |
| .policy = nlpvrdpy_policy, |
| .doit = nlpvrdpy_cmd_flipped, |
| .flags = 0, |
| .internal_flags = NLPVRDPY_CIF_NLPVRDPY |
| }, |
| { |
| .cmd = NLPVRDPY_CMD_COPIED, |
| .policy = nlpvrdpy_policy, |
| .doit = nlpvrdpy_cmd_copied, |
| .flags = 0, |
| .internal_flags = NLPVRDPY_CIF_NLPVRDPY |
| } |
| }; |
| |
| int nlpvrdpy_register(void) |
| { |
| nlpvrdpy_family.module = THIS_MODULE; |
| nlpvrdpy_family.ops = nlpvrdpy_ops; |
| nlpvrdpy_family.n_ops = ARRAY_SIZE(nlpvrdpy_ops); |
| |
| return genl_register_family(&nlpvrdpy_family); |
| } |
| |
| int nlpvrdpy_unregister(void) |
| { |
| return genl_unregister_family(&nlpvrdpy_family); |
| } |