title: How to replace set_fs(KERNEL_DS) for a kernel 5.10.x module driver version
body: <p>I've been updating a custom module driver to a 5.10.x linux kernel version. My driver adds a layer over a cdc-acm device. To replicate the behavior the next little driver is used.</p>
  <pre><code>#include &lt;linux/kernel.h&gt;
  #include &lt;linux/module.h&gt;
  #include &lt;linux/fs.h&gt;
  #include &lt;linux/file.h&gt;
  #include &lt;linux/tty.h&gt;
  #include &lt;linux/version.h&gt;
  #include &lt;linux/uaccess.h&gt;
  
  
  
  /* Device major umber */
  static int major;
  
  /* ttyACM file descriptor */
  static struct file *fd;
  
  
  /* Private functions ---------------------------------------------------------*/
  static int usbox_serial_baudrate_set(struct file *fd)
  {
      int ret;
      mm_segment_t old_fs;
      struct termios newtio;
      //struct termios __user newtio;
      //void __user *unewtio = (void __user *) &amp;newtio;
  
      memset(&amp;newtio, 0, sizeof(newtio));
      newtio.c_cflag = (B115200 | CS8 | CLOCAL | CREAD);
  
  #if LINUX_VERSION_CODE &lt; KERNEL_VERSION(5,0,0)
      old_fs = get_fs();
      set_fs( get_ds() );
  #elif LINUX_VERSION_CODE &lt; KERNEL_VERSION(5,10,0)
      old_fs = get_fs();
      set_fs( KERNEL_DS );
  #else
      old_fs = force_uaccess_begin();
  #endif
  
      if (fd-&gt;f_op-&gt;unlocked_ioctl) {
          ret = fd-&gt;f_op-&gt;unlocked_ioctl(fd, TCSETS, (unsigned long int) &amp;newtio);
          pr_info(&quot;_unlocked_ioctl: %d\n&quot;, ret);
      } else {
          ret = -ENOTTY;
      }
  
  #if LINUX_VERSION_CODE &lt; KERNEL_VERSION(5,10,0)
      set_fs(old_fs);
  #else
      force_uaccess_end(old_fs);
  #endif
  
      pr_info(&quot;ret: %d\n&quot;, ret );
      return ret;
  }
  
  
  /* Driver Methods ------------------------------------------------------------*/
  static ssize_t testdrv_read(struct file *filp,
                 char __user *buf, size_t count, loff_t *ppos)
  {
      return 0;
  }
  
  static ssize_t testdrv_write(struct file *filp,
                  const char __user *buf, size_t count, loff_t *ppos)
  {
      return 0;
  }
  
  static int testdrv_open(struct inode *inode, struct file *filp)
  {
      pr_info(&quot;testdrv_open\n&quot;);
  
      fd = filp_open( &quot;/dev/ttyACM0&quot;, O_RDWR|O_NOCTTY, 0);
      if (IS_ERR(fd)) {
          pr_info(&quot;error from filp_open()\n&quot;);
          return -ENODEV;
      }
  
      pr_info (&quot;fd      : %p\n&quot;, fd);
      pr_info (&quot;fd-&gt;f_op: %p\n&quot;, fd-&gt;f_op);
      pr_info (&quot;ioctl   : %p\n&quot;, fd-&gt;f_op-&gt;unlocked_ioctl);
  
      if ((fd-&gt;f_op == NULL) || (fd-&gt;f_op-&gt;unlocked_ioctl == NULL)) {
          pr_info(&quot;errno: ENODEV\n&quot;);
          return -ENODEV;
      }
  
      // Set baudrate.
      if (usbox_serial_baudrate_set(fd) != 0 ) {
          filp_close(fd, NULL);
          pr_info(&quot;errno: EINVAL\n&quot;);
          return -EINVAL;
      }
  
      return 0;
  }
  
  static int testdrv_release(struct inode *inode, struct file *filp)
  {
      pr_info(&quot;testdrv_release\n&quot;);
  
      if (fd != NULL) {
          filp_close(fd, NULL);
          fd = NULL;
      }
  
      return 0;
  }
  
  static struct file_operations testdrv_fops = {
      .owner      = THIS_MODULE,
      .read       = testdrv_read,
      .write      = testdrv_write,
      .open       = testdrv_open,
      .release    = testdrv_release
  };
  
  
  /* Module stuff --------------------------------------------------------------*/
  
  static int __init testdrv_init(void)
  {
      int ret;
  
      ret = register_chrdev(0, &quot;testdrv&quot;, &amp;testdrv_fops);
      if (ret &lt; 0) {
          pr_err(&quot;Error %d\n&quot;, ret);
          return ret;
      }
      major = ret;
      fd = NULL;
      pr_info(&quot;Major %d\n&quot;, major);
  
      return 0;
  }
  
  static void __exit testdrv_exit(void)
  {
      unregister_chrdev(major, &quot;testdrv&quot;);
  }
  
  module_init(testdrv_init);
  module_exit(testdrv_exit);
  
  MODULE_LICENSE(&quot;GPL&quot;);
  MODULE_AUTHOR(&quot;Unknonw&quot;);
  MODULE_DESCRIPTION(&quot;testdrv&quot;);
  </code></pre>
  <p>Here, when the testdrv is open, the driver opens the ttyACM related. Then it calls 'usbox_serial_baudrate_set' to set the baudrate. This functions calls 'unlocked_ioctl' from the fill descriptor. To be able to use this call I had to use</p>
  <pre><code>#if LINUX_VERSION_CODE &lt; KERNEL_VERSION(5,0,0)
      old_fs = get_fs();
      set_fs( get_ds() );
  #elif LINUX_VERSION_CODE &lt; KERNEL_VERSION(5,10,0)
      old_fs = get_fs();
      set_fs( KERNEL_DS );
  #else
      old_fs = force_uaccess_begin();
  #endif
  ...
          ret = fd-&gt;f_op-&gt;unlocked_ioctl(fd, TCSETS, (unsigned long int) &amp;newtio);
  ...
  #if LINUX_VERSION_CODE &lt; KERNEL_VERSION(5,10,0)
      set_fs(old_fs);
  #else
      force_uaccess_end(old_fs);
  #endif
  </code></pre>
  <p>Before 5.10.x this code worked ok. I had to do minor changes for 5.4.x using KERNEL_DS, but now I always get a EFAULT from 'unlocked_ioctl'. I've tried removing 'force_uaccess_begin / force_uaccess_end' with no luck. The 'unlocked_ioctl' finally calls to /drivers/tty/tty_ioctl.c 'set_termios' function and it fails in:</p>
  <pre><code>#ifdef TCGETS2
      } else if (opt &amp; TERMIOS_OLD) {
          if (user_termios_to_kernel_termios_1(&amp;tmp_termios,
                          (struct termios __user *)arg))
              return -EFAULT;
      } else {
  </code></pre>
  <p>The function 'user_termios_to_kernel_termios_1' is a macro to</p>
  <pre><code>#define user_termios_to_kernel_termios_1(k, u) copy_from_user(k, u, sizeof(struct termios))
  </code></pre>
  <p>With a 4.15.0 kernel I get the next messages in dmesg once inserted the module and cat the device related:</p>
  <pre><code>[370099.242677] testdrv:testdrv_init: Major 237
  [370103.032357] testdrv:testdrv_open: testdrv_open
  [370103.032635] testdrv:testdrv_open: fd      : 0000000034db75d4
  [370103.032637] testdrv:testdrv_open: fd-&gt;f_op: 00000000c761e065
  [370103.032638] testdrv:testdrv_open: ioctl   : 00000000608ed60c
  [370103.032643] testdrv:usbox_serial_baudrate_set: _unlocked_ioctl: 0
  [370103.032645] testdrv:usbox_serial_baudrate_set: ret: 0
  [370103.032685] testdrv:testdrv_release: testdrv_release
  </code></pre>
  <p>and with a 5.10.1</p>
  <pre><code>[  294.418308] testdrv:testdrv_init: got major 244
  [  296.574583] testdrv:testdrv_open: testdrv_open
  [  296.575949] testdrv:testdrv_open: fd      : 00000000c35e59c0
  [  296.575955] testdrv:testdrv_open: fd-&gt;f_op: 0000000041840a0e
  [  296.575957] testdrv:testdrv_open: ioctl   : 000000005e21689c
  [  296.575965] testdrv:usbox_serial_baudrate_set: _unlocked_ioctl: -14
  [  296.575967] testdrv:usbox_serial_baudrate_set: ret: -14
  [  296.575970] testdrv:testdrv_open: errno: EINVAL
  </code></pre>
  <p>Anybody can help me to understand what is going on? It's a problem with mixing user space / kernel space data? How can I solve it?</p>