+
+/**
+ * drm_fb_newmode - adds a user defined mode
+ * @inode: inode from the ioctl
+ * @filp: file * from the ioctl
+ * @cmd: cmd from ioctl
+ * @arg: arg from ioctl
+ *
+ * Adds a user specified mode to the kernel.
+ *
+ * Called by the user via ioctl.
+ *
+ * RETURNS:
+ * writes new mode id into arg.
+ * Zero on success, errno on failure.
+ */
+int drm_mode_addmode(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ struct drm_mode_modeinfo __user *argp = (void __user *)arg;
+ struct drm_mode_modeinfo new_mode;
+ struct drm_display_mode *user_mode;
+
+ if (copy_from_user(&new_mode, argp, sizeof(new_mode)))
+ return -EFAULT;
+
+ user_mode = drm_mode_create(dev);
+ if (!user_mode)
+ return -ENOMEM;
+
+ drm_crtc_convert_umode(user_mode, &new_mode);
+ user_mode->type |= DRM_MODE_TYPE_USERDEF;
+
+ user_mode->output_count = 0;
+
+ spin_lock(&dev->mode_config.config_lock);
+ list_add(&user_mode->head, &dev->mode_config.usermode_list);
+ spin_unlock(&dev->mode_config.config_lock);
+
+ new_mode.id = user_mode->mode_id;
+ if (copy_to_user(argp, &new_mode, sizeof(new_mode)))
+ return -EFAULT;
+
+ return 0;
+}
+
+/**
+ * drm_fb_rmmode - removes a user defined mode
+ * @inode: inode from the ioctl
+ * @filp: file * from the ioctl
+ * @cmd: cmd from ioctl
+ * @arg: arg from ioctl
+ *
+ * Remove the user defined mode specified by the user.
+ *
+ * Called by the user via ioctl
+ *
+ * RETURNS:
+ * Zero on success, errno on failure.
+ */
+int drm_mode_rmmode(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ uint32_t id = arg;
+ struct drm_display_mode *mode, *t;
+ int retcode = -EINVAL;
+
+ mode = idr_find(&dev->mode_config.crtc_idr, id);
+ if (!mode || (id != mode->mode_id))
+ return -EINVAL;
+
+ if (!(mode->type & DRM_MODE_TYPE_USERDEF))
+ return -EINVAL;
+
+ if (mode->output_count)
+ return -EINVAL;
+
+ spin_lock(&dev->mode_config.config_lock);
+ list_for_each_entry(t, &dev->mode_config.usermode_list, head) {
+ if (t == mode) {
+ list_del(&mode->head);
+ drm_mode_destroy(dev, mode);
+ retcode = 0;
+ break;
+ }
+ }
+ spin_unlock(&dev->mode_config.config_lock);
+ return retcode;
+}
+
+/**
+ * drm_fb_attachmode - Attach a user mode to an output
+ * @inode: inode from the ioctl
+ * @filp: file * from the ioctl
+ * @cmd: cmd from ioctl
+ * @arg: arg from ioctl
+ *
+ * This attaches a user specified mode to an output.
+ * Called by the user via ioctl.
+ *
+ * RETURNS:
+ * Zero on success, errno on failure.
+ */
+int drm_mode_attachmode(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ struct drm_mode_mode_cmd __user *argp = (void __user *)arg;
+ struct drm_mode_mode_cmd mode_cmd;
+ struct drm_output *output;
+ struct drm_display_mode *mode;
+ int i;
+
+ if (copy_from_user(&mode_cmd, argp, sizeof(mode_cmd)))
+ return -EFAULT;
+
+ mode = idr_find(&dev->mode_config.crtc_idr, mode_cmd.mode_id);
+ if (!mode || (mode->mode_id != mode_cmd.mode_id))
+ return -EINVAL;
+
+ output = idr_find(&dev->mode_config.crtc_idr, mode_cmd.output_id);
+ if (!output || (output->id != mode_cmd.output_id))
+ return -EINVAL;
+
+ for (i = 0; i < DRM_OUTPUT_MAX_UMODES; i++) {
+ if (output->user_mode_ids[i] == 0) {
+ output->user_mode_ids[i] = mode->mode_id;
+ mode->output_count++;
+ break;
+ }
+ }
+
+ if (i == DRM_OUTPUT_MAX_UMODES)
+ return -ENOSPC;
+
+ return 0;
+}
+
+
+/**
+ * drm_fb_detachmode - Detach a user specified mode from an output
+ * @inode: inode from the ioctl
+ * @filp: file * from the ioctl
+ * @cmd: cmd from ioctl
+ * @arg: arg from ioctl
+ *
+ * Called by the user via ioctl.
+ *
+ * RETURNS:
+ * Zero on success, errno on failure.
+ */
+int drm_mode_detachmode(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ struct drm_mode_mode_cmd __user *argp = (void __user *)arg;
+ struct drm_mode_mode_cmd mode_cmd;
+ struct drm_output *output;
+ struct drm_display_mode *mode;
+ int i, found = 0;
+
+ if (copy_from_user(&mode_cmd, argp, sizeof(mode_cmd)))
+ return -EFAULT;
+
+ mode = idr_find(&dev->mode_config.crtc_idr, mode_cmd.mode_id);
+ if (!mode || (mode->mode_id != mode_cmd.mode_id))
+ return -EINVAL;
+
+ output = idr_find(&dev->mode_config.crtc_idr, mode_cmd.output_id);
+ if (!output || (output->id != mode_cmd.output_id))
+ return -EINVAL;
+
+
+ for (i = 0; i < DRM_OUTPUT_MAX_UMODES; i++) {
+ if (output->user_mode_ids[i] == mode->mode_id) {
+ output->user_mode_ids[i] = 0;
+ mode->output_count--;
+ found = 1;
+ }
+ }
+
+ if (!found)
+ return -EINVAL;
+
+ return 0;
+}