handle_spawn (PortalFlatpak         *object,
              GDBusMethodInvocation *invocation,
              GUnixFDList           *fd_list,
              const gchar           *arg_cwd_path,
              const gchar *const    *arg_argv,
              GVariant              *arg_fds,
              GVariant              *arg_envs,
              guint                  arg_flags,
              GVariant              *arg_options)
{
  g_autoptr(GError) error = NULL;
  ChildSetupData child_setup_data = { NULL };
  GPid pid;
  PidData *pid_data;
  InstanceIdReadData *instance_id_read_data = NULL;
  gsize i, j, n_fds, n_envs;
  const gint *fds = NULL;
  gint fds_len = 0;
  g_autofree FdMapEntry *fd_map = NULL;
  gchar **env;
  gint32 max_fd;
  GKeyFile *app_info;
  g_autoptr(GPtrArray) flatpak_argv = g_ptr_array_new_with_free_func (g_free);
  g_autofree char *app_id = NULL;
  g_autofree char *branch = NULL;
  g_autofree char *arch = NULL;
  g_autofree char *app_commit = NULL;
  g_autofree char *runtime_ref = NULL;
  g_auto(GStrv) runtime_parts = NULL;
  g_autofree char *runtime_commit = NULL;
  g_autofree char *instance_path = NULL;
  g_auto(GStrv) extra_args = NULL;
  g_auto(GStrv) shares = NULL;
  g_auto(GStrv) sockets = NULL;
  g_auto(GStrv) devices = NULL;
  g_auto(GStrv) sandbox_expose = NULL;
  g_auto(GStrv) sandbox_expose_ro = NULL;
  g_autoptr(GVariant) sandbox_expose_fd = NULL;
  g_autoptr(GVariant) sandbox_expose_fd_ro = NULL;
  g_autoptr(GOutputStream) instance_id_out_stream = NULL;
  guint sandbox_flags = 0;
  gboolean sandboxed;
  gboolean expose_pids;
  gboolean share_pids;
  gboolean notify_start;
  gboolean devel;
  g_autoptr(GString) env_string = g_string_new ("");

  child_setup_data.instance_id_fd = -1;
  child_setup_data.env_fd = -1;

  if (fd_list != NULL)
    fds = g_unix_fd_list_peek_fds (fd_list, &fds_len);

  app_info = g_object_get_data (G_OBJECT (invocation), "app-info");
  g_assert (app_info != NULL);

  app_id = g_key_file_get_string (app_info,
                                  FLATPAK_METADATA_GROUP_APPLICATION,
                                  FLATPAK_METADATA_KEY_NAME, NULL);
  g_assert (app_id != NULL);

  g_debug ("spawn() called from app: '%s'", app_id);
  if (*app_id == 0)
    {
      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                             G_DBUS_ERROR_INVALID_ARGS,
                                             "org.freedesktop.portal.Flatpak.Spawn only works in a flatpak");
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  if (*arg_cwd_path == 0)
    arg_cwd_path = NULL;

  if (arg_argv == NULL || *arg_argv == NULL)
    {
      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                             G_DBUS_ERROR_INVALID_ARGS,
                                             "No command given");
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  if ((arg_flags & ~FLATPAK_SPAWN_FLAGS_ALL) != 0)
    {
      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
                                             "Unsupported flags enabled: 0x%x", arg_flags & ~FLATPAK_SPAWN_FLAGS_ALL);
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  runtime_ref = g_key_file_get_string (app_info,
                                       FLATPAK_METADATA_GROUP_APPLICATION,
                                       FLATPAK_METADATA_KEY_RUNTIME, NULL);
  if (runtime_ref == NULL)
    {
      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
                                             "No runtime found");
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  runtime_parts = g_strsplit (runtime_ref, "/", -1);

  branch = g_key_file_get_string (app_info,
                                  FLATPAK_METADATA_GROUP_INSTANCE,
                                  FLATPAK_METADATA_KEY_BRANCH, NULL);
  instance_path = g_key_file_get_string (app_info,
                                         FLATPAK_METADATA_GROUP_INSTANCE,
                                         FLATPAK_METADATA_KEY_INSTANCE_PATH, NULL);
  arch = g_key_file_get_string (app_info,
                                FLATPAK_METADATA_GROUP_INSTANCE,
                                FLATPAK_METADATA_KEY_ARCH, NULL);
  extra_args = g_key_file_get_string_list (app_info,
                                           FLATPAK_METADATA_GROUP_INSTANCE,
                                           FLATPAK_METADATA_KEY_EXTRA_ARGS, NULL, NULL);
  app_commit = g_key_file_get_string (app_info,
                                      FLATPAK_METADATA_GROUP_INSTANCE,
                                      FLATPAK_METADATA_KEY_APP_COMMIT, NULL);
  runtime_commit = g_key_file_get_string (app_info,
                                          FLATPAK_METADATA_GROUP_INSTANCE,
                                          FLATPAK_METADATA_KEY_RUNTIME_COMMIT, NULL);
  shares = g_key_file_get_string_list (app_info, FLATPAK_METADATA_GROUP_CONTEXT,
                                       FLATPAK_METADATA_KEY_SHARED, NULL, NULL);
  sockets = g_key_file_get_string_list (app_info, FLATPAK_METADATA_GROUP_CONTEXT,
                                       FLATPAK_METADATA_KEY_SOCKETS, NULL, NULL);
  devices = g_key_file_get_string_list (app_info, FLATPAK_METADATA_GROUP_CONTEXT,
                                        FLATPAK_METADATA_KEY_DEVICES, NULL, NULL);

  devel = g_key_file_get_boolean (app_info, FLATPAK_METADATA_GROUP_INSTANCE,
                                  FLATPAK_METADATA_KEY_DEVEL, NULL);

  g_variant_lookup (arg_options, "sandbox-expose", "^as", &sandbox_expose);
  g_variant_lookup (arg_options, "sandbox-expose-ro", "^as", &sandbox_expose_ro);
  g_variant_lookup (arg_options, "sandbox-flags", "u", &sandbox_flags);
  sandbox_expose_fd = g_variant_lookup_value (arg_options, "sandbox-expose-fd", G_VARIANT_TYPE ("ah"));
  sandbox_expose_fd_ro = g_variant_lookup_value (arg_options, "sandbox-expose-fd-ro", G_VARIANT_TYPE ("ah"));

  if ((sandbox_flags & ~FLATPAK_SPAWN_SANDBOX_FLAGS_ALL) != 0)
    {
      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
                                             "Unsupported sandbox flags enabled: 0x%x", arg_flags & ~FLATPAK_SPAWN_SANDBOX_FLAGS_ALL);
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  if (instance_path == NULL &&
      ((sandbox_expose != NULL && sandbox_expose[0] != NULL) ||
       (sandbox_expose_ro != NULL && sandbox_expose_ro[0] != NULL)))
    {
      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                             G_DBUS_ERROR_INVALID_ARGS,
                                             "Invalid sandbox expose, caller has no instance path");
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  for (i = 0; sandbox_expose != NULL && sandbox_expose[i] != NULL; i++)
    {
      const char *expose = sandbox_expose[i];

      g_debug ("exposing %s", expose);
      if (!is_valid_expose (expose, &error))
        {
          g_dbus_method_invocation_return_gerror (invocation, error);
          return G_DBUS_METHOD_INVOCATION_HANDLED;
        }
    }

  for (i = 0; sandbox_expose_ro != NULL && sandbox_expose_ro[i] != NULL; i++)
    {
      const char *expose = sandbox_expose_ro[i];
      g_debug ("exposing %s", expose);
      if (!is_valid_expose (expose, &error))
        {
          g_dbus_method_invocation_return_gerror (invocation, error);
          return G_DBUS_METHOD_INVOCATION_HANDLED;
        }
    }

  g_debug ("Running spawn command %s", arg_argv[0]);

  n_fds = 0;
  if (fds != NULL)
    n_fds = g_variant_n_children (arg_fds);
  fd_map = g_new0 (FdMapEntry, n_fds);

  child_setup_data.fd_map = fd_map;
  child_setup_data.fd_map_len = n_fds;

  max_fd = -1;
  for (i = 0; i < n_fds; i++)
    {
      gint32 handle, dest_fd;
      int handle_fd;

      g_variant_get_child (arg_fds, i, "{uh}", &dest_fd, &handle);

      if (handle >= fds_len || handle < 0)
        {
          g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                                 G_DBUS_ERROR_INVALID_ARGS,
                                                 "No file descriptor for handle %d",
                                                 handle);
          return G_DBUS_METHOD_INVOCATION_HANDLED;
        }

      handle_fd = fds[handle];

      fd_map[i].to = dest_fd;
      fd_map[i].from = handle_fd;
      fd_map[i].final = fd_map[i].to;

      /* If stdin/out/err is a tty we try to set it as the controlling
         tty for the app, this way we can use this to run in a terminal. */
      if ((dest_fd == 0 || dest_fd == 1 || dest_fd == 2) &&
          !child_setup_data.set_tty &&
          isatty (handle_fd))
        {
          child_setup_data.set_tty = TRUE;
          child_setup_data.tty = handle_fd;
        }

      max_fd = MAX (max_fd, fd_map[i].to);
      max_fd = MAX (max_fd, fd_map[i].from);
    }

  /* We make a second pass over the fds to find if any "to" fd index
     overlaps an already in use fd (i.e. one in the "from" category
     that are allocated randomly). If a fd overlaps "to" fd then its
     a caller issue and not our fault, so we ignore that. */
  for (i = 0; i < n_fds; i++)
    {
      int to_fd = fd_map[i].to;
      gboolean conflict = FALSE;

      /* At this point we're fine with using "from" values for this
         value (because we handle to==from in the code), or values
         that are before "i" in the fd_map (because those will be
         closed at this point when dup:ing). However, we can't
         reuse a fd that is in "from" for j > i. */
      for (j = i + 1; j < n_fds; j++)
        {
          int from_fd = fd_map[j].from;
          if (from_fd == to_fd)
            {
              conflict = TRUE;
              break;
            }
        }

      if (conflict)
        fd_map[i].to = ++max_fd;
    }

  if (arg_flags & FLATPAK_SPAWN_FLAGS_CLEAR_ENV)
    {
      char *empty[] = { NULL };
      env = g_strdupv (empty);
    }
  else
    env = g_get_environ ();

  /* Let the environment variables given by the caller override the ones
   * from extra_args. Don't add them to @env, because they are controlled
   * by our caller, which might be trying to use them to inject code into
   * flatpak(1); add them to the environment block instead.
   *
   * We don't use --env= here, so that if the values are something that
   * should not be exposed to other uids, they can remain confidential. */
  n_envs = g_variant_n_children (arg_envs);
  for (i = 0; i < n_envs; i++)
    {
      const char *var = NULL;
      const char *val = NULL;
      g_variant_get_child (arg_envs, i, "{&s&s}", &var, &val);

      if (var[0] == '\0')
        {
          g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                                 G_DBUS_ERROR_INVALID_ARGS,
                                                 "Environment variable cannot have empty name");
          return G_DBUS_METHOD_INVOCATION_HANDLED;
        }

      if (strchr (var, '=') != NULL)
        {
          g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                                 G_DBUS_ERROR_INVALID_ARGS,
                                                 "Environment variable name cannot contain '='");
          return G_DBUS_METHOD_INVOCATION_HANDLED;
        }

      g_string_append (env_string, var);
      g_string_append_c (env_string, '=');
      g_string_append (env_string, val);
      g_string_append_c (env_string, '\0');
    }

  g_ptr_array_add (flatpak_argv, g_strdup ("flatpak"));
  g_ptr_array_add (flatpak_argv, g_strdup ("run"));

  sandboxed = (arg_flags & FLATPAK_SPAWN_FLAGS_SANDBOX) != 0;

  if (sandboxed)
    {
      g_ptr_array_add (flatpak_argv, g_strdup ("--sandbox"));

      if (sandbox_flags & FLATPAK_SPAWN_SANDBOX_FLAGS_SHARE_DISPLAY)
        {
          if (sockets != NULL && g_strv_contains ((const char * const *) sockets, "wayland"))
            g_ptr_array_add (flatpak_argv, g_strdup ("--socket=wayland"));
          if (sockets != NULL && g_strv_contains ((const char * const *) sockets, "fallback-x11"))
            g_ptr_array_add (flatpak_argv, g_strdup ("--socket=fallback-x11"));
          if (sockets != NULL && g_strv_contains ((const char * const *) sockets, "x11"))
            g_ptr_array_add (flatpak_argv, g_strdup ("--socket=x11"));
          if (shares != NULL && g_strv_contains ((const char * const *) shares, "ipc") &&
              sockets != NULL && (g_strv_contains ((const char * const *) sockets, "fallback-x11") ||
                                  g_strv_contains ((const char * const *) sockets, "x11")))
            g_ptr_array_add (flatpak_argv, g_strdup ("--share=ipc"));
        }
      if (sandbox_flags & FLATPAK_SPAWN_SANDBOX_FLAGS_SHARE_SOUND)
        {
          if (sockets != NULL && g_strv_contains ((const char * const *) sockets, "pulseaudio"))
            g_ptr_array_add (flatpak_argv, g_strdup ("--socket=pulseaudio"));
        }
      if (sandbox_flags & FLATPAK_SPAWN_SANDBOX_FLAGS_SHARE_GPU)
        {
          if (devices != NULL &&
              (g_strv_contains ((const char * const *) devices, "dri") ||
               g_strv_contains ((const char * const *) devices, "all")))
            g_ptr_array_add (flatpak_argv, g_strdup ("--device=dri"));
        }
      if (sandbox_flags & FLATPAK_SPAWN_SANDBOX_FLAGS_ALLOW_DBUS)
        g_ptr_array_add (flatpak_argv, g_strdup ("--session-bus"));
      if (sandbox_flags & FLATPAK_SPAWN_SANDBOX_FLAGS_ALLOW_A11Y)
        g_ptr_array_add (flatpak_argv, g_strdup ("--a11y-bus"));
    }
  else
    {
      for (i = 0; extra_args != NULL && extra_args[i] != NULL; i++)
        {
          if (g_str_has_prefix (extra_args[i], "--env="))
            {
              const char *var_val = extra_args[i] + strlen ("--env=");

              if (var_val[0] == '\0' || var_val[0] == '=')
                {
                  g_warning ("Environment variable in extra-args has empty name");
                  continue;
                }

              if (strchr (var_val, '=') == NULL)
                {
                  g_warning ("Environment variable in extra-args has no value");
                  continue;
                }

              g_string_append (env_string, var_val);
              g_string_append_c (env_string, '\0');
            }
          else
            {
              g_ptr_array_add (flatpak_argv, g_strdup (extra_args[i]));
            }
        }
    }

  if (env_string->len > 0)
    {
      g_auto(GLnxTmpfile) env_tmpf  = { 0, };

      if (!flatpak_buffer_to_sealed_memfd_or_tmpfile (&env_tmpf, "environ",
                                                      env_string->str,
                                                      env_string->len, &error))
        {
          g_dbus_method_invocation_return_gerror (invocation, error);
          return G_DBUS_METHOD_INVOCATION_HANDLED;
        }

      child_setup_data.env_fd = glnx_steal_fd (&env_tmpf.fd);
      g_ptr_array_add (flatpak_argv,
                       g_strdup_printf ("--env-fd=%d",
                                        child_setup_data.env_fd));
    }

  expose_pids = (arg_flags & FLATPAK_SPAWN_FLAGS_EXPOSE_PIDS) != 0;
  share_pids = (arg_flags & FLATPAK_SPAWN_FLAGS_SHARE_PIDS) != 0;

  if (expose_pids || share_pids)
    {
      g_autofree char *instance_id = NULL;
      int sender_pid1 = 0;

      if (!(supports & FLATPAK_SPAWN_SUPPORT_FLAGS_EXPOSE_PIDS))
        {
          g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                                 G_DBUS_ERROR_NOT_SUPPORTED,
                                                 "Expose pids not supported with setuid bwrap");
          return G_DBUS_METHOD_INVOCATION_HANDLED;
        }

      instance_id = g_key_file_get_string (app_info,
                                           FLATPAK_METADATA_GROUP_INSTANCE,
                                           FLATPAK_METADATA_KEY_INSTANCE_ID, NULL);

      if (instance_id)
        {
          g_autoptr(FlatpakInstance) instance = flatpak_instance_new_for_id (instance_id);
          sender_pid1 = flatpak_instance_get_child_pid (instance);
        }

      if (sender_pid1 == 0)
        {
          g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                                 G_DBUS_ERROR_INVALID_ARGS,
                                                 "Could not find requesting pid");
          return G_DBUS_METHOD_INVOCATION_HANDLED;
        }

      g_ptr_array_add (flatpak_argv, g_strdup_printf ("--parent-pid=%d", sender_pid1));

      if (share_pids)
        g_ptr_array_add (flatpak_argv, g_strdup ("--parent-share-pids"));
      else
        g_ptr_array_add (flatpak_argv, g_strdup ("--parent-expose-pids"));
    }

  notify_start = (arg_flags & FLATPAK_SPAWN_FLAGS_NOTIFY_START) != 0;
  if (notify_start)
    {
      int pipe_fds[2];
      if (pipe (pipe_fds) == -1)
        {
          int errsv = errno;
          g_dbus_method_invocation_return_error (invocation, G_IO_ERROR,
                                                 g_io_error_from_errno (errsv),
                                                 "Failed to create instance ID pipe: %s",
                                                 g_strerror (errsv));
          return G_DBUS_METHOD_INVOCATION_HANDLED;
        }

      GInputStream *in_stream = G_INPUT_STREAM (g_unix_input_stream_new (pipe_fds[0], TRUE));
      /* This is saved to ensure the portal's end gets closed after the exec. */
      instance_id_out_stream = G_OUTPUT_STREAM (g_unix_output_stream_new (pipe_fds[1], TRUE));

      instance_id_read_data = g_new0 (InstanceIdReadData, 1);

      g_input_stream_read_async (in_stream, instance_id_read_data->buffer,
                                 INSTANCE_ID_BUFFER_SIZE - 1, G_PRIORITY_DEFAULT, NULL,
                                 instance_id_read_finish, instance_id_read_data);

      g_ptr_array_add (flatpak_argv, g_strdup_printf ("--instance-id-fd=%d", pipe_fds[1]));
      child_setup_data.instance_id_fd = pipe_fds[1];
    }

  if (devel)
    g_ptr_array_add (flatpak_argv, g_strdup ("--devel"));

  /* Inherit launcher network access from launcher, unless
     NO_NETWORK set. */
  if (shares != NULL && g_strv_contains ((const char * const *) shares, "network") &&
      !(arg_flags & FLATPAK_SPAWN_FLAGS_NO_NETWORK))
    g_ptr_array_add (flatpak_argv, g_strdup ("--share=network"));
  else
    g_ptr_array_add (flatpak_argv, g_strdup ("--unshare=network"));

  if (instance_path)
    {
      for (i = 0; sandbox_expose != NULL && sandbox_expose[i] != NULL; i++)
        g_ptr_array_add (flatpak_argv,
                         filesystem_sandbox_arg (instance_path, sandbox_expose[i], FALSE));
      for (i = 0; sandbox_expose_ro != NULL && sandbox_expose_ro[i] != NULL; i++)
        g_ptr_array_add (flatpak_argv,
                         filesystem_sandbox_arg (instance_path, sandbox_expose_ro[i], TRUE));
    }

  for (i = 0; sandbox_expose_ro != NULL && sandbox_expose_ro[i] != NULL; i++)
    {
      const char *expose = sandbox_expose_ro[i];
      g_debug ("exposing %s", expose);
    }

  if (sandbox_expose_fd != NULL)
    {
      gsize len = g_variant_n_children (sandbox_expose_fd);
      for (i = 0; i < len; i++)
        {
          gint32 handle;
          g_variant_get_child (sandbox_expose_fd, i, "h", &handle);
          if (handle >= 0 && handle < fds_len)
            {
              int handle_fd = fds[handle];
              g_autofree char *path = NULL;
              gboolean writable = FALSE;

              path = get_path_for_fd (handle_fd, &writable, &error);

              if (path)
                {
                  g_ptr_array_add (flatpak_argv, filesystem_arg (path, !writable));
                }
              else
                {
                  g_debug ("unable to get path for sandbox-exposed fd %d, ignoring: %s",
                           handle_fd, error->message);
                  g_clear_error (&error);
                }
            }
          else
            {
              g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                                     G_DBUS_ERROR_INVALID_ARGS,
                                                     "No file descriptor for handle %d",
                                                     handle);
              return G_DBUS_METHOD_INVOCATION_HANDLED;
            }
        }
    }

  if (sandbox_expose_fd_ro != NULL)
    {
      gsize len = g_variant_n_children (sandbox_expose_fd_ro);
      for (i = 0; i < len; i++)
        {
          gint32 handle;
          g_variant_get_child (sandbox_expose_fd_ro, i, "h", &handle);
          if (handle >= 0 && handle < fds_len)
            {
              int handle_fd = fds[handle];
              g_autofree char *path = NULL;
              gboolean writable = FALSE;

              path = get_path_for_fd (handle_fd, &writable, &error);

              if (path)
                {
                  g_ptr_array_add (flatpak_argv, filesystem_arg (path, TRUE));
                }
              else
                {
                  g_debug ("unable to get path for sandbox-exposed fd %d, ignoring: %s",
                           handle_fd, error->message);
                  g_clear_error (&error);
                }
            }
          else
            {
              g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                                     G_DBUS_ERROR_INVALID_ARGS,
                                                     "No file descriptor for handle %d",
                                                     handle);
              return G_DBUS_METHOD_INVOCATION_HANDLED;
            }
        }
    }

  g_ptr_array_add (flatpak_argv, g_strdup_printf ("--runtime=%s", runtime_parts[1]));
  g_ptr_array_add (flatpak_argv, g_strdup_printf ("--runtime-version=%s", runtime_parts[3]));

  if ((arg_flags & FLATPAK_SPAWN_FLAGS_LATEST_VERSION) == 0)
    {
      if (app_commit)
        g_ptr_array_add (flatpak_argv, g_strdup_printf ("--commit=%s", app_commit));
      if (runtime_commit)
        g_ptr_array_add (flatpak_argv, g_strdup_printf ("--runtime-commit=%s", runtime_commit));
    }

  if (arg_cwd_path != NULL)
    g_ptr_array_add (flatpak_argv, g_strdup_printf ("--cwd=%s", arg_cwd_path));

  if (arg_argv[0][0] != 0)
    g_ptr_array_add (flatpak_argv, g_strdup_printf ("--command=%s", arg_argv[0]));

  g_ptr_array_add (flatpak_argv, g_strdup_printf ("%s/%s/%s", app_id, arch ? arch : "", branch ? branch : ""));
  for (i = 1; arg_argv[i] != NULL; i++)
    g_ptr_array_add (flatpak_argv, g_strdup (arg_argv[i]));
  g_ptr_array_add (flatpak_argv, NULL);

  if (opt_verbose)
    {
      g_autoptr(GString) cmd = g_string_new ("");

      for (i = 0; flatpak_argv->pdata[i] != NULL; i++)
        {
          if (i > 0)
            g_string_append (cmd, " ");
          g_string_append (cmd, flatpak_argv->pdata[i]);
        }

      g_debug ("Starting: %s\n", cmd->str);
    }

  /* We use LEAVE_DESCRIPTORS_OPEN to work around dead-lock, see flatpak_close_fds_workaround */
  if (!g_spawn_async_with_pipes (NULL,
                                 (char **) flatpak_argv->pdata,
                                 env,
                                 G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
                                 child_setup_func, &child_setup_data,
                                 &pid,
                                 NULL,
                                 NULL,
                                 NULL,
                                 &error))
    {
      gint code = G_DBUS_ERROR_FAILED;
      if (g_error_matches (error, G_SPAWN_ERROR, G_SPAWN_ERROR_ACCES))
        code = G_DBUS_ERROR_ACCESS_DENIED;
      else if (g_error_matches (error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT))
        code = G_DBUS_ERROR_FILE_NOT_FOUND;
      g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, code,
                                             "Failed to start command: %s",
                                             error->message);
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  if (instance_id_read_data)
    instance_id_read_data->pid = pid;

  pid_data = g_new0 (PidData, 1);
  pid_data->pid = pid;
  pid_data->client = g_strdup (g_dbus_method_invocation_get_sender (invocation));
  pid_data->watch_bus = (arg_flags & FLATPAK_SPAWN_FLAGS_WATCH_BUS) != 0;
  pid_data->expose_or_share_pids = (expose_pids || share_pids);
  pid_data->child_watch = g_child_watch_add_full (G_PRIORITY_DEFAULT,
                                                  pid,
                                                  child_watch_died,
                                                  pid_data,
                                                  NULL);

  g_debug ("Client Pid is %d", pid_data->pid);

  g_hash_table_replace (client_pid_data_hash, GUINT_TO_POINTER (pid_data->pid),
                        pid_data);

  portal_flatpak_complete_spawn (object, invocation, NULL, pid);
  return G_DBUS_METHOD_INVOCATION_HANDLED;
}

portal: Do not use caller-supplied variables in environment

If the caller specifies a variable that can be used to inject arbitrary
code into processes, we must not allow it to enter the environment
block used to run `flatpak run`, which runs unsandboxed.

This change requires the previous commit "context: Add --env-fd option",
which adds infrastructure used here.

To be secure, this change also requires the previous commit
"run: Convert all environment variables into bwrap arguments", which
protects a non-setuid bwrap(1) from the same attack.

Signed-off-by: Simon McVittie <smcv@collabora.com>
Part-of: https://github.com/flatpak/flatpak/security/advisories/GHSA-4ppf-fxf6-vxg2

static GHashTable *client_pid_data_hash = NULL;

static GDBusConnection *session_bus = NULL;

static GNetworkMonitor *network_monitor = NULL;

static gboolean no_idle_exit = FALSE;

static guint name_owner_id = 0;

static PortalFlatpak *portal;

static gboolean opt_verbose;

static int opt_poll_timeout;

static gboolean opt_poll_when_metered;

static FlatpakSpawnSupportFlags supports = 0;

static guint update_monitors_timeout = 0;

static gboolean update_monitors_timeout_running_thread = FALSE;

#define PERMISSION_TABLE "flatpak"

#define INSTANCE_ID_BUFFER_SIZE 16

typedef struct {
  GMutex lock; /* This protects the closed, running and installed state */
  gboolean closed;
  gboolean running; /* While this is set, don't close the monitor */
  gboolean installing;

  char *sender;
  char *obj_path;
  GCancellable *cancellable;

  /* Static data */
  char *name;
  char *arch;
  char *branch;
  char *commit;
  char *app_path;

  /* Last reported values, starting at the instance commit */
  char *reported_local_commit;
  char *reported_remote_commit;
} UpdateMonitorData;

static gboolean           check_all_for_updates_cb (void                       *data);

static gboolean           has_update_monitors      (void);

static gboolean           handle_close             (PortalFlatpakUpdateMonitor *monitor,
                                                    GDBusMethodInvocation      *invocation);

static gboolean           handle_update            (PortalFlatpakUpdateMonitor *monitor,
                                                    GDBusMethodInvocation      *invocation,
                                                    const char                 *arg_window,
                                                    GVariant                   *arg_options);

static guint idle_timeout_id = 0;

typedef struct
{
  GPid     pid;
  char    *client;
  guint    child_watch;
  gboolean watch_bus;
  gboolean expose_or_share_pids;
} PidData;

typedef struct
{
  int from;
  int to;
  int final;
} FdMapEntry;

typedef struct
{
  FdMapEntry *fd_map;
  int         fd_map_len;
  int         instance_id_fd;
  gboolean    set_tty;
  int         tty;
  int         env_fd;
} ChildSetupData;

typedef struct
{
  guint pid;
  gchar buffer[INSTANCE_ID_BUFFER_SIZE];
} InstanceIdReadData;

typedef struct
{
  FlatpakInstance *instance;
  guint            pid;
  guint            attempt;
} BwrapinfoWatcherData;

static GHashTable *installation_cache = NULL;

typedef struct {
  GOutputStream *out;
  int n_ops;
  int op;
  int progress;
  gboolean saw_first_operation;
} TransactionData;

#define DBUS_NAME_DBUS "org.freedesktop.DBus"

#define DBUS_PATH_DBUS "/org/freedesktop/DBus"


static void write_to_phy(MSF2EmacState *s)
{
    uint8_t reg_addr = s->regs[R_MII_ADDR] & R_MII_ADDR_REGADDR_MASK;
    uint8_t phy_addr = (s->regs[R_MII_ADDR] >> R_MII_ADDR_PHYADDR_SHIFT) &
                       R_MII_ADDR_REGADDR_MASK;
    uint16_t data = s->regs[R_MII_CTL] & 0xFFFF;

    if (phy_addr != PHYADDR) {
        return;
    }

    switch (reg_addr) {
    case MII_BMCR:
        if (data & MII_BMCR_RESET) {
            /* Phy reset */
            msf2_phy_reset(s);
            data &= ~MII_BMCR_RESET;
        }
        if (data & MII_BMCR_AUTOEN) {
            /* Complete autonegotiation immediately */
            data &= ~MII_BMCR_AUTOEN;
            s->phy_regs[MII_BMSR] |= MII_BMSR_AN_COMP;
        }
        break;
    }

    s->phy_regs[reg_addr] = data;
}

msf2-mac: switch to use qemu_receive_packet() for loopback

This patch switches to use qemu_receive_packet() which can detect
reentrancy and return early.

This is intended to address CVE-2021-3416.

Cc: Prasad J Pandit <ppandit@redhat.com>
Cc: qemu-stable@nongnu.org
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Signed-off-by: Jason Wang <jasowang@redhat.com>

#define PHYADDR                 0x1

static const MemoryRegionOps emac_ops = {
    .read = emac_read,
    .write = emac_write,
    .endianness = DEVICE_NATIVE_ENDIAN,
    .impl = {
        .min_access_size = 4,
        .max_access_size = 4
    }
};

static NetClientInfo net_msf2_emac_info = {
    .type = NET_CLIENT_DRIVER_NIC,
    .size = sizeof(NICState),
    .can_receive = emac_can_rx,
    .receive = emac_rx,
    .link_status_changed = emac_set_link,
};

static Property msf2_emac_properties[] = {
    DEFINE_PROP_LINK("ahb-bus", MSF2EmacState, dma_mr,
                     TYPE_MEMORY_REGION, MemoryRegion *),
    DEFINE_NIC_PROPERTIES(MSF2EmacState, conf),
    DEFINE_PROP_END_OF_LIST(),
};

static const VMStateDescription vmstate_msf2_emac = {
    .name = TYPE_MSS_EMAC,
    .version_id = 1,
    .minimum_version_id = 1,
    .fields = (VMStateField[]) {
        VMSTATE_UINT8_ARRAY(mac_addr, MSF2EmacState, ETH_ALEN),
        VMSTATE_UINT32(rx_desc, MSF2EmacState),
        VMSTATE_UINT16_ARRAY(phy_regs, MSF2EmacState, PHY_MAX_REGS),
        VMSTATE_UINT32_ARRAY(regs, MSF2EmacState, R_MAX),
        VMSTATE_END_OF_LIST()
    }
};

static const TypeInfo msf2_emac_info = {
    .name          = TYPE_MSS_EMAC,
    .parent        = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(MSF2EmacState),
    .instance_init = msf2_emac_init,
    .class_init    = msf2_emac_class_init,
};


static void msf2_emac_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);

    dc->realize = msf2_emac_realize;
    dc->reset = msf2_emac_reset;
    dc->vmsd = &vmstate_msf2_emac;
    device_class_set_props(dc, msf2_emac_properties);
}

static const MemoryRegionOps emac_ops = {
    .read = emac_read,
    .write = emac_write,
    .endianness = DEVICE_NATIVE_ENDIAN,
    .impl = {
        .min_access_size = 4,
        .max_access_size = 4
    }
};

static NetClientInfo net_msf2_emac_info = {
    .type = NET_CLIENT_DRIVER_NIC,
    .size = sizeof(NICState),
    .can_receive = emac_can_rx,
    .receive = emac_rx,
    .link_status_changed = emac_set_link,
};

static Property msf2_emac_properties[] = {
    DEFINE_PROP_LINK("ahb-bus", MSF2EmacState, dma_mr,
                     TYPE_MEMORY_REGION, MemoryRegion *),
    DEFINE_NIC_PROPERTIES(MSF2EmacState, conf),
    DEFINE_PROP_END_OF_LIST(),
};

static const VMStateDescription vmstate_msf2_emac = {
    .name = TYPE_MSS_EMAC,
    .version_id = 1,
    .minimum_version_id = 1,
    .fields = (VMStateField[]) {
        VMSTATE_UINT8_ARRAY(mac_addr, MSF2EmacState, ETH_ALEN),
        VMSTATE_UINT32(rx_desc, MSF2EmacState),
        VMSTATE_UINT16_ARRAY(phy_regs, MSF2EmacState, PHY_MAX_REGS),
        VMSTATE_UINT32_ARRAY(regs, MSF2EmacState, R_MAX),
        VMSTATE_END_OF_LIST()
    }
};

static const TypeInfo msf2_emac_info = {
    .name          = TYPE_MSS_EMAC,
    .parent        = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(MSF2EmacState),
    .instance_init = msf2_emac_init,
    .class_init    = msf2_emac_class_init,
};


static void msf2_emac_do_reset(MSF2EmacState *s)
{
    memset(&s->regs[0], 0, sizeof(s->regs));
    s->regs[R_CFG1] = 0x80000000;
    s->regs[R_CFG2] = 0x00007000;
    s->regs[R_IFG] = 0x40605060;
    s->regs[R_HALF_DUPLEX] = 0x00A1F037;
    s->regs[R_MAX_FRAME_LENGTH] = 0x00000600;
    s->regs[R_FIFO_CFG5] = 0X3FFFF;

    msf2_phy_reset(s);
}


static void emac_set_link(NetClientState *nc)
{
    MSF2EmacState *s = qemu_get_nic_opaque(nc);

    msf2_phy_update_link(s);
}


static bool emac_can_rx(NetClientState *nc)
{
    MSF2EmacState *s = qemu_get_nic_opaque(nc);

    return (s->regs[R_CFG1] & R_CFG1_RX_EN_MASK) &&
           (s->regs[R_DMA_RX_CTL] & R_DMA_RX_CTL_EN_MASK);
}


static void emac_store_desc(MSF2EmacState *s, EmacDesc *d, hwaddr desc)
{
    /* Convert from host endianness into LE. */
    d->pktaddr = cpu_to_le32(d->pktaddr);
    d->pktsize = cpu_to_le32(d->pktsize);
    d->next = cpu_to_le32(d->next);

    address_space_write(&s->dma_as, desc, MEMTXATTRS_UNSPECIFIED, d, sizeof *d);
}

typedef struct {
    uint32_t pktaddr;
    uint32_t pktsize;
    uint32_t next;
} EmacDesc;

static const MemoryRegionOps emac_ops = {
    .read = emac_read,
    .write = emac_write,
    .endianness = DEVICE_NATIVE_ENDIAN,
    .impl = {
        .min_access_size = 4,
        .max_access_size = 4
    }
};

static NetClientInfo net_msf2_emac_info = {
    .type = NET_CLIENT_DRIVER_NIC,
    .size = sizeof(NICState),
    .can_receive = emac_can_rx,
    .receive = emac_rx,
    .link_status_changed = emac_set_link,
};

static Property msf2_emac_properties[] = {
    DEFINE_PROP_LINK("ahb-bus", MSF2EmacState, dma_mr,
                     TYPE_MEMORY_REGION, MemoryRegion *),
    DEFINE_NIC_PROPERTIES(MSF2EmacState, conf),
    DEFINE_PROP_END_OF_LIST(),
};

static const VMStateDescription vmstate_msf2_emac = {
    .name = TYPE_MSS_EMAC,
    .version_id = 1,
    .minimum_version_id = 1,
    .fields = (VMStateField[]) {
        VMSTATE_UINT8_ARRAY(mac_addr, MSF2EmacState, ETH_ALEN),
        VMSTATE_UINT32(rx_desc, MSF2EmacState),
        VMSTATE_UINT16_ARRAY(phy_regs, MSF2EmacState, PHY_MAX_REGS),
        VMSTATE_UINT32_ARRAY(regs, MSF2EmacState, R_MAX),
        VMSTATE_END_OF_LIST()
    }
};

static const TypeInfo msf2_emac_info = {
    .name          = TYPE_MSS_EMAC,
    .parent        = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(MSF2EmacState),
    .instance_init = msf2_emac_init,
    .class_init    = msf2_emac_class_init,
};


static void msf2_emac_realize(DeviceState *dev, Error **errp)
{
    MSF2EmacState *s = MSS_EMAC(dev);

    if (!s->dma_mr) {
        error_setg(errp, "MSS_EMAC 'ahb-bus' link not set");
        return;
    }

    address_space_init(&s->dma_as, s->dma_mr, "emac-ahb");

    qemu_macaddr_default_if_unset(&s->conf.macaddr);
    s->nic = qemu_new_nic(&net_msf2_emac_info, &s->conf,
                          object_get_typename(OBJECT(dev)), dev->id, s);
    qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
}


static void msf2_emac_init(Object *obj)
{
    MSF2EmacState *s = MSS_EMAC(obj);

    sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);

    memory_region_init_io(&s->mmio, obj, &emac_ops, s,
                          "msf2-emac", R_MAX * 4);
    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
}


static void msf2_phy_reset(MSF2EmacState *s)
{
    memset(&s->phy_regs[0], 0, sizeof(s->phy_regs));
    s->phy_regs[MII_BMCR] = 0x1140;
    s->phy_regs[MII_BMSR] = 0x7968;
    s->phy_regs[MII_PHYID1] = 0x0022;
    s->phy_regs[MII_PHYID2] = 0x1550;
    s->phy_regs[MII_ANAR] = 0x01E1;
    s->phy_regs[MII_ANLPAR] = 0xCDE1;

    msf2_phy_update_link(s);
}


static void msf2_phy_update_link(MSF2EmacState *s)
{
    /* Autonegotiation status mirrors link status. */
    if (qemu_get_queue(s->nic)->link_down) {
        s->phy_regs[MII_BMSR] &= ~(MII_BMSR_AN_COMP |
                                   MII_BMSR_LINK_ST);
    } else {
        s->phy_regs[MII_BMSR] |= (MII_BMSR_AN_COMP |
                                  MII_BMSR_LINK_ST);
    }
}


static void msf2_emac_reset(DeviceState *dev)
{
    MSF2EmacState *s = MSS_EMAC(dev);

    msf2_emac_do_reset(s);
}


static void emac_write(void *opaque, hwaddr addr, uint64_t val64,
        unsigned int size)
{
    MSF2EmacState *s = opaque;
    uint32_t value = val64;
    uint32_t enreqbits;
    uint8_t pktcnt;

    addr >>= 2;
    switch (addr) {
    case R_DMA_TX_CTL:
        s->regs[addr] = value;
        if (value & R_DMA_TX_CTL_EN_MASK) {
            msf2_dma_tx(s);
        }
        break;
    case R_DMA_RX_CTL:
        s->regs[addr] = value;
        if (value & R_DMA_RX_CTL_EN_MASK) {
            s->rx_desc = s->regs[R_DMA_RX_DESC];
            qemu_flush_queued_packets(qemu_get_queue(s->nic));
        }
        break;
    case R_CFG1:
        s->regs[addr] = value;
        if (value & R_CFG1_RESET_MASK) {
            msf2_emac_do_reset(s);
        }
        break;
    case R_FIFO_CFG0:
       /*
        * For our implementation, turning on modules is instantaneous,
        * so the states requested via the *ENREQ bits appear in the
        * *ENRPLY bits immediately. Also the reset bits to reset PE-MCXMAC
        * module are not emulated here since it deals with start of frames,
        * inter-packet gap and control frames.
        */
        enreqbits = extract32(value, 8, 5);
        s->regs[addr] = deposit32(value, 16, 5, enreqbits);
        break;
    case R_DMA_TX_DESC:
        if (value & 0x3) {
            qemu_log_mask(LOG_GUEST_ERROR, "Tx Descriptor address should be"
                          " 32 bit aligned\n");
        }
        /* Ignore [1:0] bits */
        s->regs[addr] = value & ~3;
        break;
    case R_DMA_RX_DESC:
        if (value & 0x3) {
            qemu_log_mask(LOG_GUEST_ERROR, "Rx Descriptor address should be"
                          " 32 bit aligned\n");
        }
        /* Ignore [1:0] bits */
        s->regs[addr] = value & ~3;
        break;
    case R_DMA_TX_STATUS:
        if (value & R_DMA_TX_STATUS_UNDERRUN_MASK) {
            s->regs[addr] &= ~R_DMA_TX_STATUS_UNDERRUN_MASK;
        }
        if (value & R_DMA_TX_STATUS_PKT_SENT_MASK) {
            pktcnt = FIELD_EX32(s->regs[addr], DMA_TX_STATUS, PKTCNT);
            pktcnt--;
            s->regs[addr] = FIELD_DP32(s->regs[addr], DMA_TX_STATUS,
                                       PKTCNT, pktcnt);
            if (pktcnt == 0) {
                s->regs[addr] &= ~R_DMA_TX_STATUS_PKT_SENT_MASK;
            }
        }
        break;
    case R_DMA_RX_STATUS:
        if (value & R_DMA_RX_STATUS_OVERFLOW_MASK) {
            s->regs[addr] &= ~R_DMA_RX_STATUS_OVERFLOW_MASK;
        }
        if (value & R_DMA_RX_STATUS_PKT_RCVD_MASK) {
            pktcnt = FIELD_EX32(s->regs[addr], DMA_RX_STATUS, PKTCNT);
            pktcnt--;
            s->regs[addr] = FIELD_DP32(s->regs[addr], DMA_RX_STATUS,
                                       PKTCNT, pktcnt);
            if (pktcnt == 0) {
                s->regs[addr] &= ~R_DMA_RX_STATUS_PKT_RCVD_MASK;
            }
        }
        break;
    case R_DMA_IRQ:
        break;
    case R_MII_CMD:
        if (value & R_MII_CMD_READ_MASK) {
            s->regs[R_MII_STS] = read_from_phy(s);
        }
        break;
    case R_MII_CTL:
        s->regs[addr] = value;
        write_to_phy(s);
        break;
    case R_STA1:
        s->regs[addr] = value;
       /*
        * R_STA1 [31:24] : octet 1 of mac address
        * R_STA1 [23:16] : octet 2 of mac address
        * R_STA1 [15:8] : octet 3 of mac address
        * R_STA1 [7:0] : octet 4 of mac address
        */
        stl_be_p(s->mac_addr, value);
        break;
    case R_STA2:
        s->regs[addr] = value;
       /*
        * R_STA2 [31:24] : octet 5 of mac address
        * R_STA2 [23:16] : octet 6 of mac address
        */
        stw_be_p(s->mac_addr + 4, value >> 16);
        break;
    default:
        if (addr >= ARRAY_SIZE(s->regs)) {
            qemu_log_mask(LOG_GUEST_ERROR,
                          "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__,
                          addr * 4);
            return;
        }
        s->regs[addr] = value;
        break;
    }
    emac_update_irq(s);
}


static bool addr_filter_ok(MSF2EmacState *s, const uint8_t *buf)
{
    /* The broadcast MAC address: FF:FF:FF:FF:FF:FF */
    const uint8_t broadcast_addr[] = { 0xFF, 0xFF, 0xFF, 0xFF,
                                              0xFF, 0xFF };
    bool bcast_en = true;
    bool mcast_en = true;

    if (s->regs[R_FIFO_CFG5] & R_FIFO_CFG5_BCAST_MASK) {
        bcast_en = true; /* Broadcast dont care for drop circuitry */
    } else if (s->regs[R_FIFO_CFG4] & R_FIFO_CFG4_BCAST_MASK) {
        bcast_en = false;
    }

    if (s->regs[R_FIFO_CFG5] & R_FIFO_CFG5_MCAST_MASK) {
        mcast_en = true; /* Multicast dont care for drop circuitry */
    } else if (s->regs[R_FIFO_CFG4] & R_FIFO_CFG4_MCAST_MASK) {
        mcast_en = false;
    }

    if (!memcmp(buf, broadcast_addr, sizeof(broadcast_addr))) {
        return bcast_en;
    }

    if (buf[0] & 1) {
        return mcast_en;
    }

    return !memcmp(buf, s->mac_addr, sizeof(s->mac_addr));
}


static uint32_t emac_get_isr(MSF2EmacState *s)
{
    uint32_t ier = s->regs[R_DMA_IRQ_MASK];
    uint32_t tx = s->regs[R_DMA_TX_STATUS] & 0xF;
    uint32_t rx = s->regs[R_DMA_RX_STATUS] & 0xF;
    uint32_t isr = (rx << 4) | tx;

    s->regs[R_DMA_IRQ] = ier & isr;
    return s->regs[R_DMA_IRQ];
}


static void msf2_emac_register_types(void)
{
    type_register_static(&msf2_emac_info);
}


static void emac_load_desc(MSF2EmacState *s, EmacDesc *d, hwaddr desc)
{
    address_space_read(&s->dma_as, desc, MEMTXATTRS_UNSPECIFIED, d, sizeof *d);
    /* Convert from LE into host endianness. */
    d->pktaddr = le32_to_cpu(d->pktaddr);
    d->pktsize = le32_to_cpu(d->pktsize);
    d->next = le32_to_cpu(d->next);
}


static void msf2_dma_tx(MSF2EmacState *s)
{
    NetClientState *nc = qemu_get_queue(s->nic);
    hwaddr desc = s->regs[R_DMA_TX_DESC];
    uint8_t buf[MAX_PKT_SIZE];
    EmacDesc d;
    int size;
    uint8_t pktcnt;
    uint32_t status;

    if (!(s->regs[R_CFG1] & R_CFG1_TX_EN_MASK)) {
        return;
    }

    while (1) {
        emac_load_desc(s, &d, desc);
        if (d.pktsize & EMPTY_MASK) {
            break;
        }
        size = d.pktsize & PKT_SIZE;
        address_space_read(&s->dma_as, d.pktaddr, MEMTXATTRS_UNSPECIFIED,
                           buf, size);
        /*
         * This is very basic way to send packets. Ideally there should be
         * a FIFO and packets should be sent out from FIFO only when
         * R_CFG1 bit 0 is set.
         */
        if (s->regs[R_CFG1] & R_CFG1_LB_EN_MASK) {
            qemu_receive_packet(nc, buf, size);
        } else {
            qemu_send_packet(nc, buf, size);
        }
        d.pktsize |= EMPTY_MASK;
        emac_store_desc(s, &d, desc);
        /* update sent packets count */
        status = s->regs[R_DMA_TX_STATUS];
        pktcnt = FIELD_EX32(status, DMA_TX_STATUS, PKTCNT);
        pktcnt++;
        s->regs[R_DMA_TX_STATUS] = FIELD_DP32(status, DMA_TX_STATUS,
                                              PKTCNT, pktcnt);
        s->regs[R_DMA_TX_STATUS] |= R_DMA_TX_STATUS_PKT_SENT_MASK;
        desc = d.next;
    }
    s->regs[R_DMA_TX_STATUS] |= R_DMA_TX_STATUS_UNDERRUN_MASK;
    s->regs[R_DMA_TX_CTL] &= ~R_DMA_TX_CTL_EN_MASK;
}

#define EMPTY_MASK              (1 << 31)

#define PKT_SIZE                0x7FF

#define MAX_PKT_SIZE            2048

typedef struct {
    uint32_t pktaddr;
    uint32_t pktsize;
    uint32_t next;
} EmacDesc;

static const MemoryRegionOps emac_ops = {
    .read = emac_read,
    .write = emac_write,
    .endianness = DEVICE_NATIVE_ENDIAN,
    .impl = {
        .min_access_size = 4,
        .max_access_size = 4
    }
};

static NetClientInfo net_msf2_emac_info = {
    .type = NET_CLIENT_DRIVER_NIC,
    .size = sizeof(NICState),
    .can_receive = emac_can_rx,
    .receive = emac_rx,
    .link_status_changed = emac_set_link,
};

static Property msf2_emac_properties[] = {
    DEFINE_PROP_LINK("ahb-bus", MSF2EmacState, dma_mr,
                     TYPE_MEMORY_REGION, MemoryRegion *),
    DEFINE_NIC_PROPERTIES(MSF2EmacState, conf),
    DEFINE_PROP_END_OF_LIST(),
};

static const VMStateDescription vmstate_msf2_emac = {
    .name = TYPE_MSS_EMAC,
    .version_id = 1,
    .minimum_version_id = 1,
    .fields = (VMStateField[]) {
        VMSTATE_UINT8_ARRAY(mac_addr, MSF2EmacState, ETH_ALEN),
        VMSTATE_UINT32(rx_desc, MSF2EmacState),
        VMSTATE_UINT16_ARRAY(phy_regs, MSF2EmacState, PHY_MAX_REGS),
        VMSTATE_UINT32_ARRAY(regs, MSF2EmacState, R_MAX),
        VMSTATE_END_OF_LIST()
    }
};

static const TypeInfo msf2_emac_info = {
    .name          = TYPE_MSS_EMAC,
    .parent        = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(MSF2EmacState),
    .instance_init = msf2_emac_init,
    .class_init    = msf2_emac_class_init,
};


static uint16_t read_from_phy(MSF2EmacState *s)
{
    uint8_t reg_addr = s->regs[R_MII_ADDR] & R_MII_ADDR_REGADDR_MASK;
    uint8_t phy_addr = (s->regs[R_MII_ADDR] >> R_MII_ADDR_PHYADDR_SHIFT) &
                       R_MII_ADDR_REGADDR_MASK;

    if (phy_addr == PHYADDR) {
        return s->phy_regs[reg_addr];
    } else {
        return 0xFFFF;
    }
}


static ssize_t emac_rx(NetClientState *nc, const uint8_t *buf, size_t size)
{
    MSF2EmacState *s = qemu_get_nic_opaque(nc);
    EmacDesc d;
    uint8_t pktcnt;
    uint32_t status;

    if (size > (s->regs[R_MAX_FRAME_LENGTH] & 0xFFFF)) {
        return size;
    }
    if (!addr_filter_ok(s, buf)) {
        return size;
    }

    emac_load_desc(s, &d, s->rx_desc);

    if (d.pktsize & EMPTY_MASK) {
        address_space_write(&s->dma_as, d.pktaddr, MEMTXATTRS_UNSPECIFIED,
                            buf, size & PKT_SIZE);
        d.pktsize = size & PKT_SIZE;
        emac_store_desc(s, &d, s->rx_desc);
        /* update received packets count */
        status = s->regs[R_DMA_RX_STATUS];
        pktcnt = FIELD_EX32(status, DMA_RX_STATUS, PKTCNT);
        pktcnt++;
        s->regs[R_DMA_RX_STATUS] = FIELD_DP32(status, DMA_RX_STATUS,
                                              PKTCNT, pktcnt);
        s->regs[R_DMA_RX_STATUS] |= R_DMA_RX_STATUS_PKT_RCVD_MASK;
        s->rx_desc = d.next;
    } else {
        s->regs[R_DMA_RX_CTL] &= ~R_DMA_RX_CTL_EN_MASK;
        s->regs[R_DMA_RX_STATUS] |= R_DMA_RX_STATUS_OVERFLOW_MASK;
    }
    emac_update_irq(s);
    return size;
}

#define EMPTY_MASK              (1 << 31)

#define PKT_SIZE                0x7FF

typedef struct {
    uint32_t pktaddr;
    uint32_t pktsize;
    uint32_t next;
} EmacDesc;

static const MemoryRegionOps emac_ops = {
    .read = emac_read,
    .write = emac_write,
    .endianness = DEVICE_NATIVE_ENDIAN,
    .impl = {
        .min_access_size = 4,
        .max_access_size = 4
    }
};

static NetClientInfo net_msf2_emac_info = {
    .type = NET_CLIENT_DRIVER_NIC,
    .size = sizeof(NICState),
    .can_receive = emac_can_rx,
    .receive = emac_rx,
    .link_status_changed = emac_set_link,
};

static Property msf2_emac_properties[] = {
    DEFINE_PROP_LINK("ahb-bus", MSF2EmacState, dma_mr,
                     TYPE_MEMORY_REGION, MemoryRegion *),
    DEFINE_NIC_PROPERTIES(MSF2EmacState, conf),
    DEFINE_PROP_END_OF_LIST(),
};

static const VMStateDescription vmstate_msf2_emac = {
    .name = TYPE_MSS_EMAC,
    .version_id = 1,
    .minimum_version_id = 1,
    .fields = (VMStateField[]) {
        VMSTATE_UINT8_ARRAY(mac_addr, MSF2EmacState, ETH_ALEN),
        VMSTATE_UINT32(rx_desc, MSF2EmacState),
        VMSTATE_UINT16_ARRAY(phy_regs, MSF2EmacState, PHY_MAX_REGS),
        VMSTATE_UINT32_ARRAY(regs, MSF2EmacState, R_MAX),
        VMSTATE_END_OF_LIST()
    }
};

static const TypeInfo msf2_emac_info = {
    .name          = TYPE_MSS_EMAC,
    .parent        = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(MSF2EmacState),
    .instance_init = msf2_emac_init,
    .class_init    = msf2_emac_class_init,
};


static void emac_update_irq(MSF2EmacState *s)
{
    bool intr = emac_get_isr(s);

    qemu_set_irq(s->irq, intr);
}


static uint64_t emac_read(void *opaque, hwaddr addr, unsigned int size)
{
    MSF2EmacState *s = opaque;
    uint32_t r = 0;

    addr >>= 2;

    switch (addr) {
    case R_DMA_IRQ:
        r = emac_get_isr(s);
        break;
    default:
        if (addr >= ARRAY_SIZE(s->regs)) {
            qemu_log_mask(LOG_GUEST_ERROR,
                          "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__,
                          addr * 4);
            return r;
        }
        r = s->regs[addr];
        break;
    }
    return r;
}


static void ca8210_spi_transfer_complete(void *context)
{
	struct cas_control *cas_ctl = context;
	struct ca8210_priv *priv = cas_ctl->priv;
	bool duplex_rx = false;
	int i;
	u8 retry_buffer[CA8210_SPI_BUF_SIZE];

	if (
		cas_ctl->tx_in_buf[0] == SPI_NACK ||
		(cas_ctl->tx_in_buf[0] == SPI_IDLE &&
		cas_ctl->tx_in_buf[1] == SPI_NACK)
	) {
		/* ca8210 is busy */
		dev_info(&priv->spi->dev, "ca8210 was busy during attempted write\n");
		if (cas_ctl->tx_buf[0] == SPI_IDLE) {
			dev_warn(
				&priv->spi->dev,
				"IRQ servicing NACKd, dropping transfer\n"
			);
			kfree(cas_ctl);
			return;
		}
		if (priv->retries > 3) {
			dev_err(&priv->spi->dev, "too many retries!\n");
			kfree(cas_ctl);
			ca8210_remove(priv->spi);
			return;
		}
		memcpy(retry_buffer, cas_ctl->tx_buf, CA8210_SPI_BUF_SIZE);
		kfree(cas_ctl);
		ca8210_spi_transfer(
			priv->spi,
			retry_buffer,
			CA8210_SPI_BUF_SIZE
		);
		priv->retries++;
		dev_info(&priv->spi->dev, "retried spi write\n");
		return;
	} else if (
			cas_ctl->tx_in_buf[0] != SPI_IDLE &&
			cas_ctl->tx_in_buf[0] != SPI_NACK
		) {
		duplex_rx = true;
	}

	if (duplex_rx) {
		dev_dbg(&priv->spi->dev, "READ CMD DURING TX\n");
		for (i = 0; i < cas_ctl->tx_in_buf[1] + 2; i++)
			dev_dbg(
				&priv->spi->dev,
				"%#03x\n",
				cas_ctl->tx_in_buf[i]
			);
		ca8210_rx_done(cas_ctl);
	}
	complete(&priv->spi_transfer_complete);
	kfree(cas_ctl);
	priv->retries = 0;
}

ieee802154: ca8210: prevent memory leak

In ca8210_probe the allocated pdata needs to be assigned to
spi_device->dev.platform_data before calling ca8210_get_platform_data.
Othrwise when ca8210_get_platform_data fails pdata cannot be released.

Signed-off-by: Navid Emamdoost <navid.emamdoost@gmail.com>
Link: https://lore.kernel.org/r/20190917224713.26371-1-navid.emamdoost@gmail.com
Signed-off-by: Stefan Schmidt <stefan@datenfreihafen.org>

#define DRIVER_NAME "ca8210"

#define CA8210_SPI_BUF_SIZE 256

#define CA8210_SYNC_TIMEOUT 1000     /* Timeout for synchronous commands [ms] */

#define SPI_IDLE                           (0xFF)

#define SPI_NACK                           (0xF0)

struct cas_control {
	struct spi_message msg;
	struct spi_transfer transfer;

	u8 tx_buf[CA8210_SPI_BUF_SIZE];
	u8 tx_in_buf[CA8210_SPI_BUF_SIZE];

	struct ca8210_priv *priv;
}

struct ca8210_test {
	struct dentry *ca8210_dfs_spi_int;
	struct kfifo up_fifo;
	wait_queue_head_t readq;
}

struct ca8210_priv {
	struct spi_device *spi;
	struct ieee802154_hw *hw;
	bool hw_registered;
	spinlock_t lock;
	struct workqueue_struct *mlme_workqueue;
	struct workqueue_struct *irq_workqueue;
	struct sk_buff *tx_skb;
	u8 nextmsduhandle;
	struct clk *clk;
	int last_dsn;
	struct ca8210_test test;
	bool async_tx_pending;
	u8 *sync_command_response;
	struct completion ca8210_is_awake;
	int sync_down, sync_up;
	struct completion spi_transfer_complete, sync_exchange_complete;
	bool promiscuous;
	int retries;
}

struct work_priv_container {
	struct work_struct work;
	struct ca8210_priv *priv;
}

struct ca8210_platform_data {
	bool extclockenable;
	unsigned int extclockfreq;
	unsigned int extclockgpio;
	int gpio_reset;
	int gpio_irq;
	int irq_id;
}

struct fulladdr {
	u8         mode;
	u8         pan_id[2];
	u8         address[8];
}

struct secspec {
	u8         security_level;
	u8         key_id_mode;
	u8         key_source[8];
	u8         key_index;
}

struct mcps_data_request_pset {
	u8              src_addr_mode;
	struct fulladdr dst;
	u8              msdu_length;
	u8              msdu_handle;
	u8              tx_options;
	u8              msdu[MAX_DATA_SIZE];
}

struct mlme_set_request_pset {
	u8         pib_attribute;
	u8         pib_attribute_index;
	u8         pib_attribute_length;
	u8         pib_attribute_value[MAX_ATTRIBUTE_SIZE];
}

struct hwme_set_request_pset {
	u8         hw_attribute;
	u8         hw_attribute_length;
	u8         hw_attribute_value[MAX_HWME_ATTRIBUTE_SIZE];
}

struct hwme_get_request_pset {
	u8         hw_attribute;
}

struct tdme_setsfr_request_pset {
	u8         sfr_page;
	u8         sfr_address;
	u8         sfr_value;
}

struct hwme_set_confirm_pset {
	u8         status;
	u8         hw_attribute;
}

struct hwme_get_confirm_pset {
	u8         status;
	u8         hw_attribute;
	u8         hw_attribute_length;
	u8         hw_attribute_value[MAX_HWME_ATTRIBUTE_SIZE];
}

struct tdme_setsfr_confirm_pset {
	u8         status;
	u8         sfr_page;
	u8         sfr_address;
}


static int ca8210_test_check_upstream(u8 *buf, void *device_ref)
{
	int ret;
	u8 response[CA8210_SPI_BUF_SIZE];

	if (buf[0] == SPI_MLME_SET_REQUEST) {
		ret = tdme_checkpibattribute(buf[2], buf[4], buf + 5);
		if (ret) {
			response[0]  = SPI_MLME_SET_CONFIRM;
			response[1] = 3;
			response[2] = MAC_INVALID_PARAMETER;
			response[3] = buf[2];
			response[4] = buf[3];
			if (cascoda_api_upstream)
				cascoda_api_upstream(response, 5, device_ref);
			return ret;
		}
	}
	if (buf[0] == SPI_MLME_ASSOCIATE_REQUEST) {
		return tdme_channelinit(buf[2], device_ref);
	} else if (buf[0] == SPI_MLME_START_REQUEST) {
		return tdme_channelinit(buf[4], device_ref);
	} else if (
		(buf[0] == SPI_MLME_SET_REQUEST) &&
		(buf[2] == PHY_CURRENT_CHANNEL)
	) {
		return tdme_channelinit(buf[5], device_ref);
	} else if (
		(buf[0] == SPI_TDME_SET_REQUEST) &&
		(buf[2] == TDME_CHANNEL)
	) {
		return tdme_channelinit(buf[4], device_ref);
	} else if (
		(CA8210_MAC_WORKAROUNDS) &&
		(buf[0] == SPI_MLME_RESET_REQUEST) &&
		(buf[2] == 1)
	) {
		/* reset COORD Bit for Channel Filtering as Coordinator */
		return tdme_setsfr_request_sync(
			0,
			CA8210_SFR_MACCON,
			0,
			device_ref
		);
	}
	return 0;
} /* End of EVBMECheckSerialCommand() */

#define CA8210_SPI_BUF_SIZE 256

#define CA8210_SYNC_TIMEOUT 1000     /* Timeout for synchronous commands [ms] */

#define MAC_INVALID_PARAMETER           (0xE8)

#define TDME_CHANNEL          (0x00)

#define PHY_CURRENT_CHANNEL               (0x00)

#define CA8210_MAC_WORKAROUNDS (0)

#define SPI_MLME_ASSOCIATE_REQUEST     (MLME_ASSOCIATE_REQUEST)

#define SPI_MLME_RESET_REQUEST         (MLME_RESET_REQUEST + SPI_SYN)

#define SPI_MLME_SET_REQUEST           (MLME_SET_REQUEST + SPI_SYN)

#define SPI_MLME_START_REQUEST         (MLME_START_REQUEST + SPI_SYN)

#define SPI_MLME_SET_CONFIRM           (MLME_SET_CONFIRM + SPI_S2M + SPI_SYN)

#define SPI_TDME_SET_REQUEST           (TDME_SET_REQUEST + SPI_SYN)

#define CA8210_SFR_MACCON                  (0xD8)

struct cas_control {
	struct spi_message msg;
	struct spi_transfer transfer;

	u8 tx_buf[CA8210_SPI_BUF_SIZE];
	u8 tx_in_buf[CA8210_SPI_BUF_SIZE];

	struct ca8210_priv *priv;
}

struct ca8210_priv {
	struct spi_device *spi;
	struct ieee802154_hw *hw;
	bool hw_registered;
	spinlock_t lock;
	struct workqueue_struct *mlme_workqueue;
	struct workqueue_struct *irq_workqueue;
	struct sk_buff *tx_skb;
	u8 nextmsduhandle;
	struct clk *clk;
	int last_dsn;
	struct ca8210_test test;
	bool async_tx_pending;
	u8 *sync_command_response;
	struct completion ca8210_is_awake;
	int sync_down, sync_up;
	struct completion spi_transfer_complete, sync_exchange_complete;
	bool promiscuous;
	int retries;
}

struct ca8210_platform_data {
	bool extclockenable;
	unsigned int extclockfreq;
	unsigned int extclockgpio;
	int gpio_reset;
	int gpio_irq;
	int irq_id;
}

static int (*cascoda_api_upstream)(
	const u8 *buf,
	size_t len,
	void *device_ref
);

static int ca8210_net_rx(
	struct ieee802154_hw  *hw,
	u8                    *command,
	size_t                 len
);

static u8 mlme_reset_request_sync(
	u8       set_default_pib,
	void    *device_ref
);

static int ca8210_spi_transfer(
	struct spi_device *spi,
	const u8          *buf,
	size_t             len
);

static int ca8210_remove(struct spi_device *spi_device);

static int (*cascoda_api_downstream)(
	const u8 *buf,
	size_t len,
	u8 *response,
	void *device_ref
) = ca8210_spi_exchange;

static const struct ieee802154_ops ca8210_phy_ops = {
	.start = ca8210_start,
	.stop = ca8210_stop,
	.xmit_async = ca8210_xmit_async,
	.ed = ca8210_get_ed,
	.set_channel = ca8210_set_channel,
	.set_hw_addr_filt = ca8210_set_hw_addr_filt,
	.set_txpower = ca8210_set_tx_power,
	.set_cca_mode = ca8210_set_cca_mode,
	.set_cca_ed_level = ca8210_set_cca_ed_level,
	.set_csma_params = ca8210_set_csma_params,
	.set_frame_retries = ca8210_set_frame_retries,
	.set_promiscuous_mode = ca8210_set_promiscuous_mode
};


static int ca8210_get_platform_data(
	struct spi_device *spi_device,
	struct ca8210_platform_data *pdata
)
{
	int ret = 0;

	if (!spi_device->dev.of_node)
		return -EINVAL;

	pdata->extclockenable = of_property_read_bool(
		spi_device->dev.of_node,
		"extclock-enable"
	);
	if (pdata->extclockenable) {
		ret = of_property_read_u32(
			spi_device->dev.of_node,
			"extclock-freq",
			&pdata->extclockfreq
		);
		if (ret < 0)
			return ret;

		ret = of_property_read_u32(
			spi_device->dev.of_node,
			"extclock-gpio",
			&pdata->extclockgpio
		);
	}

	return ret;
}

struct cas_control {
	struct spi_message msg;
	struct spi_transfer transfer;

	u8 tx_buf[CA8210_SPI_BUF_SIZE];
	u8 tx_in_buf[CA8210_SPI_BUF_SIZE];

	struct ca8210_priv *priv;
}

struct ca8210_test {
	struct dentry *ca8210_dfs_spi_int;
	struct kfifo up_fifo;
	wait_queue_head_t readq;
}

struct ca8210_priv {
	struct spi_device *spi;
	struct ieee802154_hw *hw;
	bool hw_registered;
	spinlock_t lock;
	struct workqueue_struct *mlme_workqueue;
	struct workqueue_struct *irq_workqueue;
	struct sk_buff *tx_skb;
	u8 nextmsduhandle;
	struct clk *clk;
	int last_dsn;
	struct ca8210_test test;
	bool async_tx_pending;
	u8 *sync_command_response;
	struct completion ca8210_is_awake;
	int sync_down, sync_up;
	struct completion spi_transfer_complete, sync_exchange_complete;
	bool promiscuous;
	int retries;
}

struct work_priv_container {
	struct work_struct work;
	struct ca8210_priv *priv;
}

struct ca8210_platform_data {
	bool extclockenable;
	unsigned int extclockfreq;
	unsigned int extclockgpio;
	int gpio_reset;
	int gpio_irq;
	int irq_id;
}

struct fulladdr {
	u8         mode;
	u8         pan_id[2];
	u8         address[8];
}

struct secspec {
	u8         security_level;
	u8         key_id_mode;
	u8         key_source[8];
	u8         key_index;
}

struct mcps_data_request_pset {
	u8              src_addr_mode;
	struct fulladdr dst;
	u8              msdu_length;
	u8              msdu_handle;
	u8              tx_options;
	u8              msdu[MAX_DATA_SIZE];
}

struct mlme_set_request_pset {
	u8         pib_attribute;
	u8         pib_attribute_index;
	u8         pib_attribute_length;
	u8         pib_attribute_value[MAX_ATTRIBUTE_SIZE];
}

struct hwme_set_request_pset {
	u8         hw_attribute;
	u8         hw_attribute_length;
	u8         hw_attribute_value[MAX_HWME_ATTRIBUTE_SIZE];
}

struct hwme_get_request_pset {
	u8         hw_attribute;
}

struct tdme_setsfr_request_pset {
	u8         sfr_page;
	u8         sfr_address;
	u8         sfr_value;
}

struct hwme_set_confirm_pset {
	u8         status;
	u8         hw_attribute;
}

struct hwme_get_confirm_pset {
	u8         status;
	u8         hw_attribute;
	u8         hw_attribute_length;
	u8         hw_attribute_value[MAX_HWME_ATTRIBUTE_SIZE];
}

struct tdme_setsfr_confirm_pset {
	u8         status;
	u8         sfr_page;
	u8         sfr_address;
}

struct mac_message {
	u8      command_id;
	u8      length;
	union {
		struct mcps_data_request_pset       data_req;
		struct mlme_set_request_pset        set_req;
		struct hwme_set_request_pset        hwme_set_req;
		struct hwme_get_request_pset        hwme_get_req;
		struct tdme_setsfr_request_pset     tdme_set_sfr_req;
		struct hwme_set_confirm_pset        hwme_set_cnf;
		struct hwme_get_confirm_pset        hwme_get_cnf;
		struct tdme_setsfr_confirm_pset     tdme_set_sfr_cnf;
		u8                                  u8param;
		u8                                  status;
		u8                                  payload[148];
	} pdata;
}


static int ca8210_xmit_async(struct ieee802154_hw *hw, struct sk_buff *skb)
{
	struct ca8210_priv *priv = hw->priv;
	int status;

	dev_dbg(&priv->spi->dev, "calling %s\n", __func__);

	priv->tx_skb = skb;
	priv->async_tx_pending = true;
	status = ca8210_skb_tx(skb, priv->nextmsduhandle, priv);
	return status;
}


static int ca8210_set_tx_power(struct ieee802154_hw *hw, s32 mbm)
{
	struct ca8210_priv *priv = hw->priv;

	mbm /= 100;
	return link_to_linux_err(
		mlme_set_request_sync(PHY_TRANSMIT_POWER, 0, 1, &mbm, priv->spi)
	);
}

#define PHY_TRANSMIT_POWER                (0x02)

struct cas_control {
	struct spi_message msg;
	struct spi_transfer transfer;

	u8 tx_buf[CA8210_SPI_BUF_SIZE];
	u8 tx_in_buf[CA8210_SPI_BUF_SIZE];

	struct ca8210_priv *priv;
}

struct ca8210_test {
	struct dentry *ca8210_dfs_spi_int;
	struct kfifo up_fifo;
	wait_queue_head_t readq;
}

struct ca8210_priv {
	struct spi_device *spi;
	struct ieee802154_hw *hw;
	bool hw_registered;
	spinlock_t lock;
	struct workqueue_struct *mlme_workqueue;
	struct workqueue_struct *irq_workqueue;
	struct sk_buff *tx_skb;
	u8 nextmsduhandle;
	struct clk *clk;
	int last_dsn;
	struct ca8210_test test;
	bool async_tx_pending;
	u8 *sync_command_response;
	struct completion ca8210_is_awake;
	int sync_down, sync_up;
	struct completion spi_transfer_complete, sync_exchange_complete;
	bool promiscuous;
	int retries;
}

struct work_priv_container {
	struct work_struct work;
	struct ca8210_priv *priv;
}

struct ca8210_platform_data {
	bool extclockenable;
	unsigned int extclockfreq;
	unsigned int extclockgpio;
	int gpio_reset;
	int gpio_irq;
	int irq_id;
}

struct fulladdr {
	u8         mode;
	u8         pan_id[2];
	u8         address[8];
}

struct secspec {
	u8         security_level;
	u8         key_id_mode;
	u8         key_source[8];
	u8         key_index;
}

struct mcps_data_request_pset {
	u8              src_addr_mode;
	struct fulladdr dst;
	u8              msdu_length;
	u8              msdu_handle;
	u8              tx_options;
	u8              msdu[MAX_DATA_SIZE];
}

struct mlme_set_request_pset {
	u8         pib_attribute;
	u8         pib_attribute_index;
	u8         pib_attribute_length;
	u8         pib_attribute_value[MAX_ATTRIBUTE_SIZE];
}

struct hwme_set_request_pset {
	u8         hw_attribute;
	u8         hw_attribute_length;
	u8         hw_attribute_value[MAX_HWME_ATTRIBUTE_SIZE];
}

struct hwme_get_request_pset {
	u8         hw_attribute;
}

struct tdme_setsfr_request_pset {
	u8         sfr_page;
	u8         sfr_address;
	u8         sfr_value;
}

struct hwme_set_confirm_pset {
	u8         status;
	u8         hw_attribute;
}

struct hwme_get_confirm_pset {
	u8         status;
	u8         hw_attribute;
	u8         hw_attribute_length;
	u8         hw_attribute_value[MAX_HWME_ATTRIBUTE_SIZE];
}

struct tdme_setsfr_confirm_pset {
	u8         status;
	u8         sfr_page;
	u8         sfr_address;
}

struct mac_message {
	u8      command_id;
	u8      length;
	union {
		struct mcps_data_request_pset       data_req;
		struct mlme_set_request_pset        set_req;
		struct hwme_set_request_pset        hwme_set_req;
		struct hwme_get_request_pset        hwme_get_req;
		struct tdme_setsfr_request_pset     tdme_set_sfr_req;
		struct hwme_set_confirm_pset        hwme_set_cnf;
		struct hwme_get_confirm_pset        hwme_get_cnf;
		struct tdme_setsfr_confirm_pset     tdme_set_sfr_cnf;
		u8                                  u8param;
		u8                                  status;
		u8                                  payload[148];
	} pdata;
}


static u8 tdme_chipinit(void *device_ref)
{
	u8 status = MAC_SUCCESS;
	u8 sfr_address;
	struct spi_device *spi = device_ref;
	struct preamble_cfg_sfr pre_cfg_value = {
		.timeout_symbols     = 3,
		.acquisition_symbols = 3,
		.search_symbols      = 1,
	};
	/* LNA Gain Settings */
	status = tdme_setsfr_request_sync(
		1, (sfr_address = CA8210_SFR_LNAGX40),
		LNAGX40_DEFAULT_GAIN, device_ref);
	if (status)
		goto finish;
	status = tdme_setsfr_request_sync(
		1, (sfr_address = CA8210_SFR_LNAGX41),
		LNAGX41_DEFAULT_GAIN, device_ref);
	if (status)
		goto finish;
	status = tdme_setsfr_request_sync(
		1, (sfr_address = CA8210_SFR_LNAGX42),
		LNAGX42_DEFAULT_GAIN, device_ref);
	if (status)
		goto finish;
	status = tdme_setsfr_request_sync(
		1, (sfr_address = CA8210_SFR_LNAGX43),
		LNAGX43_DEFAULT_GAIN, device_ref);
	if (status)
		goto finish;
	status = tdme_setsfr_request_sync(
		1, (sfr_address = CA8210_SFR_LNAGX44),
		LNAGX44_DEFAULT_GAIN, device_ref);
	if (status)
		goto finish;
	status = tdme_setsfr_request_sync(
		1, (sfr_address = CA8210_SFR_LNAGX45),
		LNAGX45_DEFAULT_GAIN, device_ref);
	if (status)
		goto finish;
	status = tdme_setsfr_request_sync(
		1, (sfr_address = CA8210_SFR_LNAGX46),
		LNAGX46_DEFAULT_GAIN, device_ref);
	if (status)
		goto finish;
	status = tdme_setsfr_request_sync(
		1, (sfr_address = CA8210_SFR_LNAGX47),
		LNAGX47_DEFAULT_GAIN, device_ref);
	if (status)
		goto finish;
	/* Preamble Timing Config */
	status = tdme_setsfr_request_sync(
		1, (sfr_address = CA8210_SFR_PRECFG),
		*((u8 *)&pre_cfg_value), device_ref);
	if (status)
		goto finish;
	/* Preamble Threshold High */
	status = tdme_setsfr_request_sync(
		1, (sfr_address = CA8210_SFR_PTHRH),
		PTHRH_DEFAULT_THRESHOLD, device_ref);
	if (status)
		goto finish;
	/* Tx Output Power 8 dBm */
	status = tdme_setsfr_request_sync(
		0, (sfr_address = CA8210_SFR_PACFGIB),
		PACFGIB_DEFAULT_CURRENT, device_ref);
	if (status)
		goto finish;

finish:
	if (status != MAC_SUCCESS) {
		dev_err(
			&spi->dev,
			"failed to set sfr at %#03x, status = %#03x\n",
			sfr_address,
			status
		);
	}
	return status;
}

#define MAC_SUCCESS                     (0x00)

#define CA8210_SFR_PACFGIB                 (0xFE)

#define CA8210_SFR_PTHRH                   (0xD1)

#define CA8210_SFR_PRECFG                  (0xD3)

#define CA8210_SFR_LNAGX40                 (0xE1)

#define CA8210_SFR_LNAGX41                 (0xE2)

#define CA8210_SFR_LNAGX42                 (0xE3)

#define CA8210_SFR_LNAGX43                 (0xE4)

#define CA8210_SFR_LNAGX44                 (0xE5)

#define CA8210_SFR_LNAGX45                 (0xE6)

#define CA8210_SFR_LNAGX46                 (0xE7)

#define CA8210_SFR_LNAGX47                 (0xE9)

#define PACFGIB_DEFAULT_CURRENT            (0x3F)

#define PTHRH_DEFAULT_THRESHOLD            (0x5A)

#define LNAGX40_DEFAULT_GAIN               (0x29) /* 10dB */

#define LNAGX41_DEFAULT_GAIN               (0x54) /* 21dB */

#define LNAGX42_DEFAULT_GAIN               (0x6C) /* 27dB */

#define LNAGX43_DEFAULT_GAIN               (0x7A) /* 30dB */

#define LNAGX44_DEFAULT_GAIN               (0x84) /* 33dB */

#define LNAGX45_DEFAULT_GAIN               (0x8B) /* 34dB */

#define LNAGX46_DEFAULT_GAIN               (0x92) /* 36dB */

#define LNAGX47_DEFAULT_GAIN               (0x96) /* 37dB */

struct cas_control {
	struct spi_message msg;
	struct spi_transfer transfer;

	u8 tx_buf[CA8210_SPI_BUF_SIZE];
	u8 tx_in_buf[CA8210_SPI_BUF_SIZE];

	struct ca8210_priv *priv;
}

struct ca8210_test {
	struct dentry *ca8210_dfs_spi_int;
	struct kfifo up_fifo;
	wait_queue_head_t readq;
}

struct ca8210_priv {
	struct spi_device *spi;
	struct ieee802154_hw *hw;
	bool hw_registered;
	spinlock_t lock;
	struct workqueue_struct *mlme_workqueue;
	struct workqueue_struct *irq_workqueue;
	struct sk_buff *tx_skb;
	u8 nextmsduhandle;
	struct clk *clk;
	int last_dsn;
	struct ca8210_test test;
	bool async_tx_pending;
	u8 *sync_command_response;
	struct completion ca8210_is_awake;
	int sync_down, sync_up;
	struct completion spi_transfer_complete, sync_exchange_complete;
	bool promiscuous;
	int retries;
}

struct work_priv_container {
	struct work_struct work;
	struct ca8210_priv *priv;
}

struct ca8210_platform_data {
	bool extclockenable;
	unsigned int extclockfreq;
	unsigned int extclockgpio;
	int gpio_reset;
	int gpio_irq;
	int irq_id;
}

struct fulladdr {
	u8         mode;
	u8         pan_id[2];
	u8         address[8];
}

struct secspec {
	u8         security_level;
	u8         key_id_mode;
	u8         key_source[8];
	u8         key_index;
}


static int ca8210_interrupt_init(struct spi_device *spi)
{
	int ret;
	struct ca8210_platform_data *pdata = spi->dev.platform_data;

	pdata->gpio_irq = of_get_named_gpio(
		spi->dev.of_node,
		"irq-gpio",
		0
	);

	pdata->irq_id = gpio_to_irq(pdata->gpio_irq);
	if (pdata->irq_id < 0) {
		dev_crit(
			&spi->dev,
			"Could not get irq for gpio pin %d\n",
			pdata->gpio_irq
		);
		gpio_free(pdata->gpio_irq);
		return pdata->irq_id;
	}

	ret = request_irq(
		pdata->irq_id,
		ca8210_interrupt_handler,
		IRQF_TRIGGER_FALLING,
		"ca8210-irq",
		spi_get_drvdata(spi)
	);
	if (ret) {
		dev_crit(&spi->dev, "request_irq %d failed\n", pdata->irq_id);
		gpio_unexport(pdata->gpio_irq);
		gpio_free(pdata->gpio_irq);
	}

	return ret;
}

#define DRIVER_NAME "ca8210"

#define CA8210_SYNC_TIMEOUT 1000     /* Timeout for synchronous commands [ms] */

struct cas_control {
	struct spi_message msg;
	struct spi_transfer transfer;

	u8 tx_buf[CA8210_SPI_BUF_SIZE];
	u8 tx_in_buf[CA8210_SPI_BUF_SIZE];

	struct ca8210_priv *priv;
}

struct ca8210_test {
	struct dentry *ca8210_dfs_spi_int;
	struct kfifo up_fifo;
	wait_queue_head_t readq;
}

struct ca8210_priv {
	struct spi_device *spi;
	struct ieee802154_hw *hw;
	bool hw_registered;
	spinlock_t lock;
	struct workqueue_struct *mlme_workqueue;
	struct workqueue_struct *irq_workqueue;
	struct sk_buff *tx_skb;
	u8 nextmsduhandle;
	struct clk *clk;
	int last_dsn;
	struct ca8210_test test;
	bool async_tx_pending;
	u8 *sync_command_response;
	struct completion ca8210_is_awake;
	int sync_down, sync_up;
	struct completion spi_transfer_complete, sync_exchange_complete;
	bool promiscuous;
	int retries;
}

struct work_priv_container {
	struct work_struct work;
	struct ca8210_priv *priv;
}

struct ca8210_platform_data {
	bool extclockenable;
	unsigned int extclockfreq;
	unsigned int extclockgpio;
	int gpio_reset;
	int gpio_irq;
	int irq_id;
}

struct fulladdr {
	u8         mode;
	u8         pan_id[2];
	u8         address[8];
}

struct secspec {
	u8         security_level;
	u8         key_id_mode;
	u8         key_source[8];
	u8         key_index;
}

struct mcps_data_request_pset {
	u8              src_addr_mode;
	struct fulladdr dst;
	u8              msdu_length;
	u8              msdu_handle;
	u8              tx_options;
	u8              msdu[MAX_DATA_SIZE];
}

struct mlme_set_request_pset {
	u8         pib_attribute;
	u8         pib_attribute_index;
	u8         pib_attribute_length;
	u8         pib_attribute_value[MAX_ATTRIBUTE_SIZE];
}

struct hwme_set_request_pset {
	u8         hw_attribute;
	u8         hw_attribute_length;
	u8         hw_attribute_value[MAX_HWME_ATTRIBUTE_SIZE];
}

struct hwme_get_request_pset {
	u8         hw_attribute;
}

struct tdme_setsfr_request_pset {
	u8         sfr_page;
	u8         sfr_address;
	u8         sfr_value;
}

struct hwme_set_confirm_pset {
	u8         status;
	u8         hw_attribute;
}

struct hwme_get_confirm_pset {
	u8         status;
	u8         hw_attribute;
	u8         hw_attribute_length;
	u8         hw_attribute_value[MAX_HWME_ATTRIBUTE_SIZE];
}

struct tdme_setsfr_confirm_pset {
	u8         status;
	u8         sfr_page;
	u8         sfr_address;
}

struct mac_message {
	u8      command_id;
	u8      length;
	union {
		struct mcps_data_request_pset       data_req;
		struct mlme_set_request_pset        set_req;
		struct hwme_set_request_pset        hwme_set_req;
		struct hwme_get_request_pset        hwme_get_req;
		struct tdme_setsfr_request_pset     tdme_set_sfr_req;
		struct hwme_set_confirm_pset        hwme_set_cnf;
		struct hwme_get_confirm_pset        hwme_get_cnf;
		struct tdme_setsfr_confirm_pset     tdme_set_sfr_cnf;
		u8                                  u8param;
		u8                                  status;
		u8                                  payload[148];
	} pdata;
}


static u8 tdme_checkpibattribute(
	u8            pib_attribute,
	u8            pib_attribute_length,
	const void   *pib_attribute_value
)
{
	u8 status = MAC_SUCCESS;
	u8 value;

	value  = *((u8 *)pib_attribute_value);

	switch (pib_attribute) {
	/* PHY */
	case PHY_TRANSMIT_POWER:
		if (value > 0x3F)
			status = MAC_INVALID_PARAMETER;
		break;
	case PHY_CCA_MODE:
		if (value > 0x03)
			status = MAC_INVALID_PARAMETER;
		break;
	/* MAC */
	case MAC_BATT_LIFE_EXT_PERIODS:
		if (value < 6 || value > 41)
			status = MAC_INVALID_PARAMETER;
		break;
	case MAC_BEACON_PAYLOAD:
		if (pib_attribute_length > MAX_BEACON_PAYLOAD_LENGTH)
			status = MAC_INVALID_PARAMETER;
		break;
	case MAC_BEACON_PAYLOAD_LENGTH:
		if (value > MAX_BEACON_PAYLOAD_LENGTH)
			status = MAC_INVALID_PARAMETER;
		break;
	case MAC_BEACON_ORDER:
		if (value > 15)
			status = MAC_INVALID_PARAMETER;
		break;
	case MAC_MAX_BE:
		if (value < 3 || value > 8)
			status = MAC_INVALID_PARAMETER;
		break;
	case MAC_MAX_CSMA_BACKOFFS:
		if (value > 5)
			status = MAC_INVALID_PARAMETER;
		break;
	case MAC_MAX_FRAME_RETRIES:
		if (value > 7)
			status = MAC_INVALID_PARAMETER;
		break;
	case MAC_MIN_BE:
		if (value > 8)
			status = MAC_INVALID_PARAMETER;
		break;
	case MAC_RESPONSE_WAIT_TIME:
		if (value < 2 || value > 64)
			status = MAC_INVALID_PARAMETER;
		break;
	case MAC_SUPERFRAME_ORDER:
		if (value > 15)
			status = MAC_INVALID_PARAMETER;
		break;
	/* boolean */
	case MAC_ASSOCIATED_PAN_COORD:
	case MAC_ASSOCIATION_PERMIT:
	case MAC_AUTO_REQUEST:
	case MAC_BATT_LIFE_EXT:
	case MAC_GTS_PERMIT:
	case MAC_PROMISCUOUS_MODE:
	case MAC_RX_ON_WHEN_IDLE:
	case MAC_SECURITY_ENABLED:
		if (value > 1)
			status = MAC_INVALID_PARAMETER;
		break;
	/* MAC SEC */
	case MAC_AUTO_REQUEST_SECURITY_LEVEL:
		if (value > 7)
			status = MAC_INVALID_PARAMETER;
		break;
	case MAC_AUTO_REQUEST_KEY_ID_MODE:
		if (value > 3)
			status = MAC_INVALID_PARAMETER;
		break;
	default:
		break;
	}

	return status;
}

#define MAC_SUCCESS                     (0x00)

#define MAC_INVALID_PARAMETER           (0xE8)

#define PHY_TRANSMIT_POWER                (0x02)

#define PHY_CCA_MODE                      (0x03)

#define MAC_ASSOCIATION_PERMIT            (0x41)

#define MAC_AUTO_REQUEST                  (0x42)

#define MAC_BATT_LIFE_EXT                 (0x43)

#define MAC_BATT_LIFE_EXT_PERIODS         (0x44)

#define MAC_BEACON_PAYLOAD                (0x45)

#define MAC_BEACON_PAYLOAD_LENGTH         (0x46)

#define MAC_BEACON_ORDER                  (0x47)

#define MAC_GTS_PERMIT                    (0x4d)

#define MAC_MAX_CSMA_BACKOFFS             (0x4e)

#define MAC_MIN_BE                        (0x4f)

#define MAC_PROMISCUOUS_MODE              (0x51)

#define MAC_RX_ON_WHEN_IDLE               (0x52)

#define MAC_SUPERFRAME_ORDER              (0x54)

#define MAC_ASSOCIATED_PAN_COORD          (0x56)

#define MAC_MAX_BE                        (0x57)

#define MAC_MAX_FRAME_RETRIES             (0x59)

#define MAC_RESPONSE_WAIT_TIME            (0x5A)

#define MAC_SECURITY_ENABLED              (0x5D)

#define MAC_AUTO_REQUEST_SECURITY_LEVEL   (0x78)

#define MAC_AUTO_REQUEST_KEY_ID_MODE      (0x79)

#define MAX_BEACON_PAYLOAD_LENGTH  (IEEE802154_MTU - MAX_BEACON_OVERHEAD)

struct mlme_set_request_pset {
	u8         pib_attribute;
	u8         pib_attribute_index;
	u8         pib_attribute_length;
	u8         pib_attribute_value[MAX_ATTRIBUTE_SIZE];
}

struct hwme_set_confirm_pset {
	u8         status;
	u8         hw_attribute;
}

struct hwme_get_confirm_pset {
	u8         status;
	u8         hw_attribute;
	u8         hw_attribute_length;
	u8         hw_attribute_value[MAX_HWME_ATTRIBUTE_SIZE];
}

struct tdme_setsfr_confirm_pset {
	u8         status;
	u8         sfr_page;
	u8         sfr_address;
}

struct mac_message {
	u8      command_id;
	u8      length;
	union {
		struct mcps_data_request_pset       data_req;
		struct mlme_set_request_pset        set_req;
		struct hwme_set_request_pset        hwme_set_req;
		struct hwme_get_request_pset        hwme_get_req;
		struct tdme_setsfr_request_pset     tdme_set_sfr_req;
		struct hwme_set_confirm_pset        hwme_set_cnf;
		struct hwme_get_confirm_pset        hwme_get_cnf;
		struct tdme_setsfr_confirm_pset     tdme_set_sfr_cnf;
		u8                                  u8param;
		u8                                  status;
		u8                                  payload[148];
	} pdata;
}

static int (*cascoda_api_upstream)(
	const u8 *buf,
	size_t len,
	void *device_ref
);

static int ca8210_net_rx(
	struct ieee802154_hw  *hw,
	u8                    *command,
	size_t                 len
);

static u8 mlme_reset_request_sync(
	u8       set_default_pib,
	void    *device_ref
);

static int ca8210_spi_transfer(
	struct spi_device *spi,
	const u8          *buf,
	size_t             len
);

static int ca8210_remove(struct spi_device *spi_device);

static int (*cascoda_api_downstream)(
	const u8 *buf,
	size_t len,
	u8 *response,
	void *device_ref
) = ca8210_spi_exchange;


static u8 tdme_setsfr_request_sync(
	u8            sfr_page,
	u8            sfr_address,
	u8            sfr_value,
	void         *device_ref
)
{
	int ret;
	struct mac_message command, response;
	struct spi_device *spi = device_ref;

	command.command_id = SPI_TDME_SETSFR_REQUEST;
	command.length = 3;
	command.pdata.tdme_set_sfr_req.sfr_page    = sfr_page;
	command.pdata.tdme_set_sfr_req.sfr_address = sfr_address;
	command.pdata.tdme_set_sfr_req.sfr_value   = sfr_value;
	response.command_id = SPI_IDLE;
	ret = cascoda_api_downstream(
		&command.command_id,
		command.length + 2,
		&response.command_id,
		device_ref
	);
	if (ret) {
		dev_crit(&spi->dev, "cascoda_api_downstream returned %d", ret);
		return MAC_SYSTEM_ERROR;
	}

	if (response.command_id != SPI_TDME_SETSFR_CONFIRM) {
		dev_crit(
			&spi->dev,
			"sync response to SPI_TDME_SETSFR_REQUEST was not SPI_TDME_SETSFR_CONFIRM, it was %d\n",
			response.command_id
		);
		return MAC_SYSTEM_ERROR;
	}

	return response.pdata.tdme_set_sfr_cnf.status;
}

#define MAC_SYSTEM_ERROR                (0xFF)

#define SPI_IDLE                           (0xFF)

#define SPI_TDME_SETSFR_REQUEST        (TDME_SETSFR_REQUEST + SPI_SYN)

#define SPI_TDME_SETSFR_CONFIRM        (TDME_SETSFR_CONFIRM + SPI_S2M + SPI_SYN)

struct cas_control {
	struct spi_message msg;
	struct spi_transfer transfer;

	u8 tx_buf[CA8210_SPI_BUF_SIZE];
	u8 tx_in_buf[CA8210_SPI_BUF_SIZE];

	struct ca8210_priv *priv;
}

struct ca8210_test {
	struct dentry *ca8210_dfs_spi_int;
	struct kfifo up_fifo;
	wait_queue_head_t readq;
}

struct ca8210_priv {
	struct spi_device *spi;
	struct ieee802154_hw *hw;
	bool hw_registered;
	spinlock_t lock;
	struct workqueue_struct *mlme_workqueue;
	struct workqueue_struct *irq_workqueue;
	struct sk_buff *tx_skb;
	u8 nextmsduhandle;
	struct clk *clk;
	int last_dsn;
	struct ca8210_test test;
	bool async_tx_pending;
	u8 *sync_command_response;
	struct completion ca8210_is_awake;
	int sync_down, sync_up;
	struct completion spi_transfer_complete, sync_exchange_complete;
	bool promiscuous;
	int retries;
}

struct work_priv_container {
	struct work_struct work;
	struct ca8210_priv *priv;
}

struct ca8210_platform_data {
	bool extclockenable;
	unsigned int extclockfreq;
	unsigned int extclockgpio;
	int gpio_reset;
	int gpio_irq;
	int irq_id;
}

struct fulladdr {
	u8         mode;
	u8         pan_id[2];
	u8         address[8];
}

struct secspec {
	u8         security_level;
	u8         key_id_mode;
	u8         key_source[8];
	u8         key_index;
}

struct mcps_data_request_pset {
	u8              src_addr_mode;
	struct fulladdr dst;
	u8              msdu_length;
	u8              msdu_handle;
	u8              tx_options;
	u8              msdu[MAX_DATA_SIZE];
}

struct mlme_set_request_pset {
	u8         pib_attribute;
	u8         pib_attribute_index;
	u8         pib_attribute_length;
	u8         pib_attribute_value[MAX_ATTRIBUTE_SIZE];
}

struct hwme_set_request_pset {
	u8         hw_attribute;
	u8         hw_attribute_length;
	u8         hw_attribute_value[MAX_HWME_ATTRIBUTE_SIZE];
}

struct hwme_get_request_pset {
	u8         hw_attribute;
}

struct tdme_setsfr_request_pset {
	u8         sfr_page;
	u8         sfr_address;
	u8         sfr_value;
}

struct hwme_set_confirm_pset {
	u8         status;
	u8         hw_attribute;
}

struct hwme_get_confirm_pset {
	u8         status;
	u8         hw_attribute;
	u8         hw_attribute_length;
	u8         hw_attribute_value[MAX_HWME_ATTRIBUTE_SIZE];
}

struct tdme_setsfr_confirm_pset {
	u8         status;
	u8         sfr_page;
	u8         sfr_address;
}


static int ca8210_test_int_driver_write(
	const u8       *buf,
	size_t          len,
	void           *spi
)
{
	struct ca8210_priv *priv = spi_get_drvdata(spi);
	struct ca8210_test *test = &priv->test;
	char *fifo_buffer;
	int i;

	dev_dbg(
		&priv->spi->dev,
		"test_interface: Buffering upstream message:\n"
	);
	for (i = 0; i < len; i++)
		dev_dbg(&priv->spi->dev, "%#03x\n", buf[i]);

	fifo_buffer = kmemdup(buf, len, GFP_KERNEL);
	if (!fifo_buffer)
		return -ENOMEM;
	kfifo_in(&test->up_fifo, &fifo_buffer, 4);
	wake_up_interruptible(&priv->test.readq);

	return 0;
}

#define CA8210_SYNC_TIMEOUT 1000     /* Timeout for synchronous commands [ms] */

#define CA8210_TEST_INT_FILE_NAME "ca8210_test"

struct cas_control {
	struct spi_message msg;
	struct spi_transfer transfer;

	u8 tx_buf[CA8210_SPI_BUF_SIZE];
	u8 tx_in_buf[CA8210_SPI_BUF_SIZE];

	struct ca8210_priv *priv;
}

struct ca8210_test {
	struct dentry *ca8210_dfs_spi_int;
	struct kfifo up_fifo;
	wait_queue_head_t readq;
}

struct ca8210_priv {
	struct spi_device *spi;
	struct ieee802154_hw *hw;
	bool hw_registered;
	spinlock_t lock;
	struct workqueue_struct *mlme_workqueue;
	struct workqueue_struct *irq_workqueue;
	struct sk_buff *tx_skb;
	u8 nextmsduhandle;
	struct clk *clk;
	int last_dsn;
	struct ca8210_test test;
	bool async_tx_pending;
	u8 *sync_command_response;
	struct completion ca8210_is_awake;
	int sync_down, sync_up;
	struct completion spi_transfer_complete, sync_exchange_complete;
	bool promiscuous;
	int retries;
}

struct work_priv_container {
	struct work_struct work;
	struct ca8210_priv *priv;
}

struct ca8210_platform_data {
	bool extclockenable;
	unsigned int extclockfreq;
	unsigned int extclockgpio;
	int gpio_reset;
	int gpio_irq;
	int irq_id;
}

struct fulladdr {
	u8         mode;
	u8         pan_id[2];
	u8         address[8];
}

struct secspec {
	u8         security_level;
	u8         key_id_mode;
	u8         key_source[8];
	u8         key_index;
}

struct mcps_data_request_pset {
	u8              src_addr_mode;
	struct fulladdr dst;
	u8              msdu_length;
	u8              msdu_handle;
	u8              tx_options;
	u8              msdu[MAX_DATA_SIZE];
}

struct mlme_set_request_pset {
	u8         pib_attribute;
	u8         pib_attribute_index;
	u8         pib_attribute_length;
	u8         pib_attribute_value[MAX_ATTRIBUTE_SIZE];
}

struct hwme_set_request_pset {
	u8         hw_attribute;
	u8         hw_attribute_length;
	u8         hw_attribute_value[MAX_HWME_ATTRIBUTE_SIZE];
}

struct hwme_get_request_pset {
	u8         hw_attribute;
}

struct tdme_setsfr_request_pset {
	u8         sfr_page;
	u8         sfr_address;
	u8         sfr_value;
}

struct hwme_set_confirm_pset {
	u8         status;
	u8         hw_attribute;
}

struct hwme_get_confirm_pset {
	u8         status;
	u8         hw_attribute;
	u8         hw_attribute_length;
	u8         hw_attribute_value[MAX_HWME_ATTRIBUTE_SIZE];
}

struct tdme_setsfr_confirm_pset {
	u8         status;
	u8         sfr_page;
	u8         sfr_address;
}


static int ca8210_config_extern_clk(
	struct ca8210_platform_data *pdata,
	struct spi_device *spi,
	bool on
)
{
	u8 clkparam[2];

	if (on) {
		dev_info(&spi->dev, "Switching external clock on\n");
		switch (pdata->extclockfreq) {
		case SIXTEEN_MHZ:
			clkparam[0] = 1;
			break;
		case EIGHT_MHZ:
			clkparam[0] = 2;
			break;
		case FOUR_MHZ:
			clkparam[0] = 3;
			break;
		case TWO_MHZ:
			clkparam[0] = 4;
			break;
		case ONE_MHZ:
			clkparam[0] = 5;
			break;
		default:
			dev_crit(&spi->dev, "Invalid extclock-freq\n");
			return -EINVAL;
		}
		clkparam[1] = pdata->extclockgpio;
	} else {
		dev_info(&spi->dev, "Switching external clock off\n");
		clkparam[0] = 0; /* off */
		clkparam[1] = 0;
	}
	return link_to_linux_err(
		hwme_set_request_sync(HWME_SYSCLKOUT, 2, clkparam, spi)
	);
}

#define ONE_MHZ      1000000

#define TWO_MHZ      (2 * ONE_MHZ)

#define FOUR_MHZ     (4 * ONE_MHZ)

#define EIGHT_MHZ    (8 * ONE_MHZ)

#define SIXTEEN_MHZ  (16 * ONE_MHZ)

#define HWME_SYSCLKOUT         (0x0F)

struct cas_control {
	struct spi_message msg;
	struct spi_transfer transfer;

	u8 tx_buf[CA8210_SPI_BUF_SIZE];
	u8 tx_in_buf[CA8210_SPI_BUF_SIZE];

	struct ca8210_priv *priv;
}

struct ca8210_test {
	struct dentry *ca8210_dfs_spi_int;
	struct kfifo up_fifo;
	wait_queue_head_t readq;
}

struct ca8210_priv {
	struct spi_device *spi;
	struct ieee802154_hw *hw;
	bool hw_registered;
	spinlock_t lock;
	struct workqueue_struct *mlme_workqueue;
	struct workqueue_struct *irq_workqueue;
	struct sk_buff *tx_skb;
	u8 nextmsduhandle;
	struct clk *clk;
	int last_dsn;
	struct ca8210_test test;
	bool async_tx_pending;
	u8 *sync_command_response;
	struct completion ca8210_is_awake;
	int sync_down, sync_up;
	struct completion spi_transfer_complete, sync_exchange_complete;
	bool promiscuous;
	int retries;
}

struct work_priv_container {
	struct work_struct work;
	struct ca8210_priv *priv;
}

struct ca8210_platform_data {
	bool extclockenable;
	unsigned int extclockfreq;
	unsigned int extclockgpio;
	int gpio_reset;
	int gpio_irq;
	int irq_id;
}

struct fulladdr {
	u8         mode;
	u8         pan_id[2];
	u8         address[8];
}

struct secspec {
	u8         security_level;
	u8         key_id_mode;
	u8         key_source[8];
	u8         key_index;
}

struct mcps_data_request_pset {
	u8              src_addr_mode;
	struct fulladdr dst;
	u8              msdu_length;
	u8              msdu_handle;
	u8              tx_options;
	u8              msdu[MAX_DATA_SIZE];
}

struct mlme_set_request_pset {
	u8         pib_attribute;
	u8         pib_attribute_index;
	u8         pib_attribute_length;
	u8         pib_attribute_value[MAX_ATTRIBUTE_SIZE];
}

struct hwme_set_request_pset {
	u8         hw_attribute;
	u8         hw_attribute_length;
	u8         hw_attribute_value[MAX_HWME_ATTRIBUTE_SIZE];
}

struct hwme_get_request_pset {
	u8         hw_attribute;
}

struct tdme_setsfr_request_pset {
	u8         sfr_page;
	u8         sfr_address;
	u8         sfr_value;
}

struct hwme_set_confirm_pset {
	u8         status;
	u8         hw_attribute;
}

struct hwme_get_confirm_pset {
	u8         status;
	u8         hw_attribute;
	u8         hw_attribute_length;
	u8         hw_attribute_value[MAX_HWME_ATTRIBUTE_SIZE];
}

struct tdme_setsfr_confirm_pset {
	u8         status;
	u8         sfr_page;
	u8         sfr_address;
}


static int ca8210_set_cca_ed_level(struct ieee802154_hw *hw, s32 level)
{
	u8 status;
	u8 ed_threshold = (level / 100) * 2 + 256;
	struct ca8210_priv *priv = hw->priv;

	status = hwme_set_request_sync(
		HWME_EDTHRESHOLD,
		1,
		&ed_threshold,
		priv->spi
	);
	if (status) {
		dev_err(
			&priv->spi->dev,
			"error setting ed threshold, HWME-SET.confirm status = %d",
			status
		);
	}
	return link_to_linux_err(status);
}

#define HWME_EDTHRESHOLD       (0x04)

struct cas_control {
	struct spi_message msg;
	struct spi_transfer transfer;

	u8 tx_buf[CA8210_SPI_BUF_SIZE];
	u8 tx_in_buf[CA8210_SPI_BUF_SIZE];

	struct ca8210_priv *priv;
}

struct ca8210_test {
	struct dentry *ca8210_dfs_spi_int;
	struct kfifo up_fifo;
	wait_queue_head_t readq;
}

struct ca8210_priv {
	struct spi_device *spi;
	struct ieee802154_hw *hw;
	bool hw_registered;
	spinlock_t lock;
	struct workqueue_struct *mlme_workqueue;
	struct workqueue_struct *irq_workqueue;
	struct sk_buff *tx_skb;
	u8 nextmsduhandle;
	struct clk *clk;
	int last_dsn;
	struct ca8210_test test;
	bool async_tx_pending;
	u8 *sync_command_response;
	struct completion ca8210_is_awake;
	int sync_down, sync_up;
	struct completion spi_transfer_complete, sync_exchange_complete;
	bool promiscuous;
	int retries;
}

struct work_priv_container {
	struct work_struct work;
	struct ca8210_priv *priv;
}

struct ca8210_platform_data {
	bool extclockenable;
	unsigned int extclockfreq;
	unsigned int extclockgpio;
	int gpio_reset;
	int gpio_irq;
	int irq_id;
}

struct fulladdr {
	u8         mode;
	u8         pan_id[2];
	u8         address[8];
}

struct secspec {
	u8         security_level;
	u8         key_id_mode;
	u8         key_source[8];
	u8         key_index;
}

struct mcps_data_request_pset {
	u8              src_addr_mode;
	struct fulladdr dst;
	u8              msdu_length;
	u8              msdu_handle;
	u8              tx_options;
	u8              msdu[MAX_DATA_SIZE];
}

struct mlme_set_request_pset {
	u8         pib_attribute;
	u8         pib_attribute_index;
	u8         pib_attribute_length;
	u8         pib_attribute_value[MAX_ATTRIBUTE_SIZE];
}

struct hwme_set_request_pset {
	u8         hw_attribute;
	u8         hw_attribute_length;
	u8         hw_attribute_value[MAX_HWME_ATTRIBUTE_SIZE];
}

struct hwme_get_request_pset {
	u8         hw_attribute;
}

struct tdme_setsfr_request_pset {
	u8         sfr_page;
	u8         sfr_address;
	u8         sfr_value;
}

struct hwme_set_confirm_pset {
	u8         status;
	u8         hw_attribute;
}

struct hwme_get_confirm_pset {
	u8         status;
	u8         hw_attribute;
	u8         hw_attribute_length;
	u8         hw_attribute_value[MAX_HWME_ATTRIBUTE_SIZE];
}

struct tdme_setsfr_confirm_pset {
	u8         status;
	u8         sfr_page;
	u8         sfr_address;
}

struct mac_message {
	u8      command_id;
	u8      length;
	union {
		struct mcps_data_request_pset       data_req;
		struct mlme_set_request_pset        set_req;
		struct hwme_set_request_pset        hwme_set_req;
		struct hwme_get_request_pset        hwme_get_req;
		struct tdme_setsfr_request_pset     tdme_set_sfr_req;
		struct hwme_set_confirm_pset        hwme_set_cnf;
		struct hwme_get_confirm_pset        hwme_get_cnf;
		struct tdme_setsfr_confirm_pset     tdme_set_sfr_cnf;
		u8                                  u8param;
		u8                                  status;
		u8                                  payload[148];
	} pdata;
}


static void ca8210_test_interface_clear(struct ca8210_priv *priv)
{
	struct ca8210_test *test = &priv->test;

	debugfs_remove(test->ca8210_dfs_spi_int);
	kfifo_free(&test->up_fifo);
	dev_info(&priv->spi->dev, "Test interface removed\n");
}

#define CA8210_TEST_INT_FILE_NAME "ca8210_test"

struct cas_control {
	struct spi_message msg;
	struct spi_transfer transfer;

	u8 tx_buf[CA8210_SPI_BUF_SIZE];
	u8 tx_in_buf[CA8210_SPI_BUF_SIZE];

	struct ca8210_priv *priv;
}

struct ca8210_test {
	struct dentry *ca8210_dfs_spi_int;
	struct kfifo up_fifo;
	wait_queue_head_t readq;
}

struct ca8210_priv {
	struct spi_device *spi;
	struct ieee802154_hw *hw;
	bool hw_registered;
	spinlock_t lock;
	struct workqueue_struct *mlme_workqueue;
	struct workqueue_struct *irq_workqueue;
	struct sk_buff *tx_skb;
	u8 nextmsduhandle;
	struct clk *clk;
	int last_dsn;
	struct ca8210_test test;
	bool async_tx_pending;
	u8 *sync_command_response;
	struct completion ca8210_is_awake;
	int sync_down, sync_up;
	struct completion spi_transfer_complete, sync_exchange_complete;
	bool promiscuous;
	int retries;
}

struct work_priv_container {
	struct work_struct work;
	struct ca8210_priv *priv;
}

struct ca8210_platform_data {
	bool extclockenable;
	unsigned int extclockfreq;
	unsigned int extclockgpio;
	int gpio_reset;
	int gpio_irq;
	int irq_id;
}

struct fulladdr {
	u8         mode;
	u8         pan_id[2];
	u8         address[8];
}

struct secspec {
	u8         security_level;
	u8         key_id_mode;
	u8         key_source[8];
	u8         key_index;
}

struct mcps_data_request_pset {
	u8              src_addr_mode;
	struct fulladdr dst;
	u8              msdu_length;
	u8              msdu_handle;
	u8              tx_options;
	u8              msdu[MAX_DATA_SIZE];
}

struct mlme_set_request_pset {
	u8         pib_attribute;
	u8         pib_attribute_index;
	u8         pib_attribute_length;
	u8         pib_attribute_value[MAX_ATTRIBUTE_SIZE];
}

struct hwme_set_request_pset {
	u8         hw_attribute;
	u8         hw_attribute_length;
	u8         hw_attribute_value[MAX_HWME_ATTRIBUTE_SIZE];
}

struct hwme_get_request_pset {
	u8         hw_attribute;
}

struct tdme_setsfr_request_pset {
	u8         sfr_page;
	u8         sfr_address;
	u8         sfr_value;
}

struct hwme_set_confirm_pset {
	u8         status;
	u8         hw_attribute;
}

struct hwme_get_confirm_pset {
	u8         status;
	u8         hw_attribute;
	u8         hw_attribute_length;
	u8         hw_attribute_value[MAX_HWME_ATTRIBUTE_SIZE];
}

struct tdme_setsfr_confirm_pset {
	u8         status;
	u8         sfr_page;
	u8         sfr_address;
}

struct mac_message {
	u8      command_id;
	u8      length;
	union {
		struct mcps_data_request_pset       data_req;
		struct mlme_set_request_pset        set_req;
		struct hwme_set_request_pset        hwme_set_req;
		struct hwme_get_request_pset        hwme_get_req;
		struct tdme_setsfr_request_pset     tdme_set_sfr_req;
		struct hwme_set_confirm_pset        hwme_set_cnf;
		struct hwme_get_confirm_pset        hwme_get_cnf;
		struct tdme_setsfr_confirm_pset     tdme_set_sfr_cnf;
		u8                                  u8param;
		u8                                  status;
		u8                                  payload[148];
	} pdata;
}


static int ca8210_start(struct ieee802154_hw *hw)
{
	int status;
	u8 rx_on_when_idle;
	u8 lqi_threshold = 0;
	struct ca8210_priv *priv = hw->priv;

	priv->last_dsn = -1;
	/* Turn receiver on when idle for now just to test rx */
	rx_on_when_idle = 1;
	status = mlme_set_request_sync(
		MAC_RX_ON_WHEN_IDLE,
		0,
		1,
		&rx_on_when_idle,
		priv->spi
	);
	if (status) {
		dev_crit(
			&priv->spi->dev,
			"Setting rx_on_when_idle failed, status = %d\n",
			status
		);
		return link_to_linux_err(status);
	}
	status = hwme_set_request_sync(
		HWME_LQILIMIT,
		1,
		&lqi_threshold,
		priv->spi
	);
	if (status) {
		dev_crit(
			&priv->spi->dev,
			"Setting lqilimit failed, status = %d\n",
			status
		);
		return link_to_linux_err(status);
	}

	return 0;
}

#define CA8210_SYNC_TIMEOUT 1000     /* Timeout for synchronous commands [ms] */

#define HWME_LQILIMIT          (0x11)

#define MAC_RX_ON_WHEN_IDLE               (0x52)

struct cas_control {
	struct spi_message msg;
	struct spi_transfer transfer;

	u8 tx_buf[CA8210_SPI_BUF_SIZE];
	u8 tx_in_buf[CA8210_SPI_BUF_SIZE];

	struct ca8210_priv *priv;
}

struct ca8210_test {
	struct dentry *ca8210_dfs_spi_int;
	struct kfifo up_fifo;
	wait_queue_head_t readq;
}

struct ca8210_priv {
	struct spi_device *spi;
	struct ieee802154_hw *hw;
	bool hw_registered;
	spinlock_t lock;
	struct workqueue_struct *mlme_workqueue;
	struct workqueue_struct *irq_workqueue;
	struct sk_buff *tx_skb;
	u8 nextmsduhandle;
	struct clk *clk;
	int last_dsn;
	struct ca8210_test test;
	bool async_tx_pending;
	u8 *sync_command_response;
	struct completion ca8210_is_awake;
	int sync_down, sync_up;
	struct completion spi_transfer_complete, sync_exchange_complete;
	bool promiscuous;
	int retries;
}

struct work_priv_container {
	struct work_struct work;
	struct ca8210_priv *priv;
}

struct ca8210_platform_data {
	bool extclockenable;
	unsigned int extclockfreq;
	unsigned int extclockgpio;
	int gpio_reset;
	int gpio_irq;
	int irq_id;
}

struct fulladdr {
	u8         mode;
	u8         pan_id[2];
	u8         address[8];
}

struct secspec {
	u8         security_level;
	u8         key_id_mode;
	u8         key_source[8];
	u8         key_index;
}

struct mcps_data_request_pset {
	u8              src_addr_mode;
	struct fulladdr dst;
	u8              msdu_length;
	u8              msdu_handle;
	u8              tx_options;
	u8              msdu[MAX_DATA_SIZE];
}

struct mlme_set_request_pset {
	u8         pib_attribute;
	u8         pib_attribute_index;
	u8         pib_attribute_length;
	u8         pib_attribute_value[MAX_ATTRIBUTE_SIZE];
}

struct hwme_set_request_pset {
	u8         hw_attribute;
	u8         hw_attribute_length;
	u8         hw_attribute_value[MAX_HWME_ATTRIBUTE_SIZE];
}

struct hwme_get_request_pset {
	u8         hw_attribute;
}

struct tdme_setsfr_request_pset {
	u8         sfr_page;
	u8         sfr_address;
	u8         sfr_value;
}

struct hwme_set_confirm_pset {
	u8         status;
	u8         hw_attribute;
}

struct hwme_get_confirm_pset {
	u8         status;
	u8         hw_attribute;
	u8         hw_attribute_length;
	u8         hw_attribute_value[MAX_HWME_ATTRIBUTE_SIZE];
}

struct tdme_setsfr_confirm_pset {
	u8         status;
	u8         sfr_page;
	u8         sfr_address;
}