1047 lines
26 KiB
C
Executable file
1047 lines
26 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2010 Samsung Electronics.
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/if_arp.h>
|
|
#include <linux/ip.h>
|
|
#include <linux/if_ether.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/device.h>
|
|
#include <linux/module.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/slab.h>
|
|
#if IS_ENABLED(CONFIG_DEBUG_SNAPSHOT)
|
|
#include <soc/samsung/debug-snapshot.h>
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_EXYNOS_S2MPU)
|
|
#include <soc/samsung/exynos-s2mpu.h>
|
|
#endif
|
|
#include "gnss_prj.h"
|
|
#include "gnss_utils.h"
|
|
|
|
#define WAKE_TIME (HZ/2) /* 500 msec */
|
|
|
|
static inline void iodev_lock_wlock(struct io_device *iod)
|
|
{
|
|
if (iod->waketime > 0 && !gnssif_wake_lock_active(iod->ws)) {
|
|
gnssif_wake_unlock(iod->ws);
|
|
gnssif_wake_lock_timeout(iod->ws, iod->waketime);
|
|
}
|
|
}
|
|
|
|
static inline int queue_skb_to_iod(struct sk_buff *skb, struct io_device *iod)
|
|
{
|
|
struct sk_buff_head *rxq = &iod->sk_rx_q;
|
|
|
|
if (rxq->qlen > MAX_IOD_RXQ_LEN) {
|
|
gif_err_limited("%s: %s application may be dead (rxq->qlen %d > %d)\n",
|
|
iod->name, iod->app ? iod->app : "corresponding",
|
|
rxq->qlen, MAX_IOD_RXQ_LEN);
|
|
dev_kfree_skb_any(skb);
|
|
return -ENOSPC;
|
|
}
|
|
|
|
skb_queue_tail(rxq, skb);
|
|
gif_debug("%s: rxq->qlen = %d\n", iod->name, rxq->qlen);
|
|
wake_up(&iod->wq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int rx_frame_with_link_header(struct sk_buff *skb)
|
|
{
|
|
struct exynos_link_header *hdr;
|
|
|
|
/* Remove EXYNOS link header */
|
|
hdr = (struct exynos_link_header *)skb->data;
|
|
skb_pull(skb, EXYNOS_HEADER_SIZE);
|
|
|
|
#if defined(DEBUG_GNSS_IPC_PKT)
|
|
/* Print received data from GNSS */
|
|
gnss_log_ipc_pkt(skb, RX);
|
|
#endif
|
|
|
|
return queue_skb_to_iod(skb, skbpriv(skb)->iod);
|
|
}
|
|
|
|
static int rx_fmt_ipc(struct sk_buff *skb)
|
|
{
|
|
return rx_frame_with_link_header(skb);
|
|
}
|
|
|
|
static int rx_demux(struct link_device *ld, struct sk_buff *skb)
|
|
{
|
|
struct io_device *iod;
|
|
|
|
iod = ld->iod;
|
|
if (unlikely(!iod)) {
|
|
gif_err("%s: ERR! no iod!\n", ld->name);
|
|
return -ENODEV;
|
|
}
|
|
|
|
skbpriv(skb)->ld = ld;
|
|
skbpriv(skb)->iod = iod;
|
|
|
|
if (atomic_read(&iod->opened) <= 0) {
|
|
gif_err_limited("%s: ERR! %s is not opened\n", ld->name, iod->name);
|
|
return -ENODEV;
|
|
}
|
|
|
|
return rx_fmt_ipc(skb);
|
|
}
|
|
|
|
static int rx_frame_done(struct io_device *iod, struct link_device *ld,
|
|
struct sk_buff *skb)
|
|
{
|
|
/* Cut off the padding of the current frame */
|
|
skb_trim(skb, exynos_get_frame_len(skb->data));
|
|
gif_debug("%s->%s: frame length = %d\n", ld->name, iod->name, skb->len);
|
|
|
|
return rx_demux(ld, skb);
|
|
}
|
|
|
|
static int recv_frame_from_skb(struct io_device *iod, struct link_device *ld,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct sk_buff *clone;
|
|
unsigned int rest;
|
|
unsigned int rcvd;
|
|
unsigned int tot; /* total length including padding */
|
|
int err = 0;
|
|
|
|
/*
|
|
* If there is only one EXYNOS frame in @skb, receive the EXYNOS frame and
|
|
* return immediately. In this case, the frame verification must already
|
|
* have been done at the link device.
|
|
*/
|
|
if (skbpriv(skb)->single_frame) {
|
|
err = rx_frame_done(iod, ld, skb);
|
|
if (err < 0)
|
|
goto exit;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The routine from here is used only if there may be multiple EXYNOS
|
|
* frames in @skb.
|
|
*/
|
|
|
|
/* Check the config field of the first frame in @skb */
|
|
if (!exynos_start_valid(skb->data)) {
|
|
gif_err("%s->%s: ERR! INVALID config 0x%02X\n",
|
|
ld->name, iod->name, skb->data[0]);
|
|
err = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
/* Get the total length of the frame with a padding */
|
|
tot = exynos_get_total_len(skb->data);
|
|
|
|
/* Verify the total length of the first frame */
|
|
rest = skb->len;
|
|
if (unlikely(tot > rest)) {
|
|
gif_err("%s->%s: ERR! tot %d > skb->len %d)\n",
|
|
ld->name, iod->name, tot, rest);
|
|
err = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
/* If there is only one EXYNOS frame in @skb, */
|
|
if (likely(tot == rest)) {
|
|
/* Receive the EXYNOS frame and return immediately */
|
|
err = rx_frame_done(iod, ld, skb);
|
|
if (err < 0)
|
|
goto exit;
|
|
return 0;
|
|
}
|
|
|
|
/* This routine is used only if there are multiple EXYNOS frames in @skb */
|
|
rcvd = 0;
|
|
while (rest > 0) {
|
|
clone = skb_clone(skb, GFP_ATOMIC);
|
|
if (unlikely(!clone)) {
|
|
gif_err("%s->%s: ERR! skb_clone fail\n",
|
|
ld->name, iod->name);
|
|
err = -ENOMEM;
|
|
goto exit;
|
|
}
|
|
|
|
/* Get the start of an EXYNOS frame */
|
|
skb_pull(clone, rcvd);
|
|
if (!exynos_start_valid(clone->data)) {
|
|
gif_err("%s->%s: ERR! INVALID config 0x%02X\n",
|
|
ld->name, iod->name, clone->data[0]);
|
|
dev_kfree_skb_any(clone);
|
|
err = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
/* Get the total length of the current frame with a padding */
|
|
tot = exynos_get_total_len(clone->data);
|
|
if (unlikely(tot > rest)) {
|
|
gif_err("%s->%s: ERR! dirty frame (tot %d > rest %d)\n",
|
|
ld->name, iod->name, tot, rest);
|
|
dev_kfree_skb_any(clone);
|
|
err = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
/* Cut off the padding of the current frame */
|
|
skb_trim(clone, exynos_get_frame_len(clone->data));
|
|
|
|
/* Demux the frame */
|
|
err = rx_demux(ld, clone);
|
|
if (err < 0) {
|
|
gif_err("%s->%s: ERR! rx_demux fail (err %d)\n",
|
|
ld->name, iod->name, err);
|
|
dev_kfree_skb_any(clone);
|
|
goto exit;
|
|
}
|
|
|
|
/* Calculate the start of the next frame */
|
|
rcvd += tot;
|
|
|
|
/* Calculate the rest size of data in @skb */
|
|
rest -= tot;
|
|
}
|
|
|
|
exit:
|
|
dev_kfree_skb_any(skb);
|
|
return err;
|
|
}
|
|
|
|
/* called from link device when a packet arrives for this io device */
|
|
static int io_dev_recv_skb_from_link_dev(struct io_device *iod,
|
|
struct link_device *ld, struct sk_buff *skb)
|
|
{
|
|
int err;
|
|
|
|
iodev_lock_wlock(iod);
|
|
|
|
err = recv_frame_from_skb(iod, ld, skb);
|
|
if (err < 0) {
|
|
gif_err("%s->%s: ERR! recv_frame_from_skb fail(err %d)\n",
|
|
ld->name, iod->name, err);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/* called from link device when a packet arrives fo this io device */
|
|
static int io_dev_recv_skb_single_from_link_dev(struct io_device *iod,
|
|
struct link_device *ld, struct sk_buff *skb)
|
|
{
|
|
int err;
|
|
|
|
if (unlikely(atomic_read(&iod->opened) <= 0)) {
|
|
gif_err_limited("%s<-%s: ERR! %s is not opened\n",
|
|
iod->name, ld->name, iod->name);
|
|
return -ENODEV;
|
|
}
|
|
|
|
iodev_lock_wlock(iod);
|
|
|
|
if (skbpriv(skb)->lnk_hdr)
|
|
skb_trim(skb, exynos_get_frame_len(skb->data));
|
|
|
|
err = rx_demux(ld, skb);
|
|
if (err < 0)
|
|
gif_err_limited("%s<-%s: ERR! rx_demux fail (err %d)\n",
|
|
iod->name, ld->name, err);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int misc_open(struct inode *inode, struct file *filp)
|
|
{
|
|
struct io_device *iod = to_io_device(filp->private_data);
|
|
struct link_device *ld;
|
|
int ref_cnt;
|
|
filp->private_data = (void *)iod;
|
|
|
|
ld = iod->ld;
|
|
|
|
ref_cnt = atomic_inc_return(&iod->opened);
|
|
|
|
gif_info("%s (opened %d) by %s\n", iod->name, ref_cnt, current->comm);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int misc_release(struct inode *inode, struct file *filp)
|
|
{
|
|
struct io_device *iod = (struct io_device *)filp->private_data;
|
|
|
|
if (atomic_dec_and_test(&iod->opened))
|
|
skb_queue_purge(&iod->sk_rx_q);
|
|
|
|
gif_info("%s (opened %d) by %s\n", iod->name, atomic_read(&iod->opened), current->comm);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int misc_poll(struct file *filp, struct poll_table_struct *wait)
|
|
{
|
|
struct io_device *iod = (struct io_device *)filp->private_data;
|
|
struct gnss_ctl *gc = iod->gc;
|
|
poll_wait(filp, &iod->wq, wait);
|
|
|
|
#if IS_ENABLED(CONFIG_USB_CONFIGFS_F_MBIM)
|
|
if (gc->is_irq_received == true) {
|
|
gif_err("POLL wakeup for power on/off interrupt\n");
|
|
return POLLPRI;
|
|
}
|
|
#endif
|
|
|
|
if (!skb_queue_empty(&iod->sk_rx_q) && gc->gnss_state == STATE_ONLINE)
|
|
return POLLIN | POLLRDNORM;
|
|
|
|
if (gc->gnss_state == STATE_OFFLINE || gc->gnss_state == STATE_FAULT) {
|
|
gif_err("POLL wakeup in abnormal state!!!\n");
|
|
return POLLHUP;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int valid_cmd_arg(unsigned int cmd, unsigned long arg)
|
|
{
|
|
switch (cmd) {
|
|
case GNSS_IOCTL_RESET:
|
|
case GNSS_IOCTL_LOAD_FIRMWARE:
|
|
case GNSS_IOCTL_REQ_FAULT_INFO:
|
|
case GNSS_IOCTL_REQ_BCMD:
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0))
|
|
return access_ok((const void *)arg, sizeof(arg));
|
|
#else
|
|
return access_ok(VERIFY_READ, (const void *)arg, sizeof(arg));
|
|
#endif
|
|
case GNSS_IOCTL_READ_FIRMWARE:
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0))
|
|
return access_ok((const void *)arg, sizeof(arg));
|
|
#else
|
|
return access_ok(VERIFY_WRITE, (const void *)arg, sizeof(arg));
|
|
#endif
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
static int send_bcmd(struct io_device *iod, unsigned long arg)
|
|
{
|
|
struct gnss_ctl *gc = iod->gc;
|
|
struct kepler_bcmd_args bcmd_args;
|
|
int err = 0;
|
|
|
|
memset(&bcmd_args, 0, sizeof(struct kepler_bcmd_args));
|
|
err = copy_from_user(&bcmd_args, (const void __user *)arg,
|
|
sizeof(struct kepler_bcmd_args));
|
|
if (err) {
|
|
gif_err("copy_from_user fail(to get structure)\n");
|
|
err = -EFAULT;
|
|
goto bcmd_exit;
|
|
}
|
|
|
|
if (!gc->ops.req_bcmd) {
|
|
gif_err("%s: !ld->req_bcmd\n", iod->name);
|
|
err = -EFAULT;
|
|
goto bcmd_exit;
|
|
}
|
|
|
|
gif_info("flags:%d, cmd_id:%d, param1:0x%08x, param2:%d(0x%08x)\n",
|
|
bcmd_args.flags, bcmd_args.cmd_id, bcmd_args.param1,
|
|
bcmd_args.param2, bcmd_args.param2);
|
|
|
|
err = gc->ops.req_bcmd(gc, bcmd_args.cmd_id, bcmd_args.flags,
|
|
bcmd_args.param1, bcmd_args.param2);
|
|
if (err == -EIO) { /* BCMD timeout */
|
|
gif_err("BCMD timeout cmd_id : %d\n", bcmd_args.cmd_id);
|
|
} else if (err == -EPERM) {
|
|
gif_err("BCMD failed due to invalid state\n");
|
|
} else {
|
|
bcmd_args.ret_val = err;
|
|
err = copy_to_user((void __user *)arg,
|
|
(void *)&bcmd_args, sizeof(bcmd_args));
|
|
if (err) {
|
|
gif_err("copy_to_user fail(to send bcmd params)\n");
|
|
err = -EFAULT;
|
|
}
|
|
}
|
|
|
|
bcmd_exit:
|
|
return err;
|
|
}
|
|
|
|
static int gnss_load_firmware(struct io_device *iod,
|
|
struct kepler_firmware_args firmware_arg)
|
|
{
|
|
struct link_device *ld = iod->ld;
|
|
#if IS_ENABLED(CONFIG_EXYNOS_S2MPU) && !IS_ENABLED(CONFIG_SOC_S5E8825)
|
|
struct gnss_pdata *pdata = iod->gc->pdata;
|
|
unsigned long ret;
|
|
#endif
|
|
int err;
|
|
|
|
gif_info("Load Firmware - fw size : %d, fw_offset : %d\n",
|
|
firmware_arg.firmware_size, firmware_arg.offset);
|
|
|
|
if (!ld->copy_reserved_from_user) {
|
|
gif_err("No copy_reserved_from_user method\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
err = ld->copy_reserved_from_user(iod->ld, firmware_arg.offset,
|
|
firmware_arg.firmware_bin, firmware_arg.firmware_size);
|
|
if (err) {
|
|
gif_err("Unable to load firmware\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_EXYNOS_S2MPU) && !IS_ENABLED(CONFIG_SOC_S5E8825)
|
|
ret = exynos_verify_subsystem_fw("GNSS", 0,
|
|
pdata->shmem_base + pdata->code_offset,
|
|
firmware_arg.firmware_size, pdata->code_allowed_size);
|
|
if (ret) {
|
|
gif_err("Failed FW verification ret:%lu\n", ret);
|
|
return -EIO;
|
|
}
|
|
|
|
ret = exynos_request_fw_stage2_ap("GNSS");
|
|
if (ret) {
|
|
gif_err("Failed stage 2 access permission ret:%lu\n", ret);
|
|
return -EACCES;
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parsing_load_firmware(struct io_device *iod, unsigned long arg)
|
|
{
|
|
struct kepler_firmware_args firmware_arg;
|
|
int err = 0;
|
|
struct gnss_pdata *pdata = iod->gc->pdata;
|
|
|
|
memset(&firmware_arg, 0, sizeof(struct kepler_firmware_args));
|
|
err = copy_from_user(&firmware_arg, (const void __user *)arg,
|
|
sizeof(struct kepler_firmware_args));
|
|
if (err) {
|
|
gif_err("copy_from_user fail(to get structure)\n");
|
|
err = -EFAULT;
|
|
return err;
|
|
}
|
|
if ((firmware_arg.offset < pdata->code_offset) ||
|
|
(pdata->code_allowed_size < (firmware_arg.offset - pdata->code_offset))) {
|
|
gif_err("wrong offset to download firmware:0x%x 0x%x 0x%x\n",
|
|
firmware_arg.offset, pdata->code_offset, pdata->code_allowed_size);
|
|
err = -EFAULT;
|
|
return err;
|
|
}
|
|
if (firmware_arg.firmware_size > pdata->code_allowed_size) {
|
|
gif_err("size too big to download:0x%x 0x%x\n",
|
|
firmware_arg.firmware_size, pdata->code_allowed_size);
|
|
err = -EFAULT;
|
|
return err;
|
|
}
|
|
|
|
gif_info("FIRMWARE OFFSET: 0x%08x SIZE: 0x%08x\n", firmware_arg.offset,
|
|
firmware_arg.firmware_size);
|
|
return gnss_load_firmware(iod, firmware_arg);
|
|
}
|
|
|
|
static int gnss_load_data(struct io_device *iod,
|
|
struct kepler_data_args data_arg)
|
|
{
|
|
struct link_device *ld = iod->ld;
|
|
int err = 0;
|
|
|
|
gif_info("Load Configuration Data - size : %d, offset : %d\n",
|
|
data_arg.size, data_arg.offset);
|
|
|
|
if (!ld->copy_reserved_from_user) {
|
|
gif_err("No copy_reserved_from_user method\n");
|
|
err = -EFAULT;
|
|
goto load_data_exit;
|
|
}
|
|
|
|
err = ld->copy_reserved_from_user(iod->ld, data_arg.offset,
|
|
data_arg.data, data_arg.size);
|
|
if (err) {
|
|
gif_err("Unable to load data\n");
|
|
err = -EFAULT;
|
|
goto load_data_exit;
|
|
}
|
|
|
|
load_data_exit:
|
|
return err;
|
|
}
|
|
|
|
static int parsing_load_data(struct io_device *iod, unsigned long arg)
|
|
{
|
|
struct kepler_data_args data_arg;
|
|
int err = 0;
|
|
struct gnss_pdata *pdata = iod->gc->pdata;
|
|
|
|
memset(&data_arg, 0, sizeof(struct kepler_data_args));
|
|
err = copy_from_user(&data_arg, (const void __user *)arg,
|
|
sizeof(struct kepler_data_args));
|
|
if (err) {
|
|
gif_err("copy_from_user fail(to get structure)\n");
|
|
err = -EFAULT;
|
|
return err;
|
|
}
|
|
if ((data_arg.offset < pdata->code_offset) ||
|
|
(pdata->code_allowed_size < (data_arg.offset - pdata->code_offset))) {
|
|
gif_err("wrong offset to download configuration data:0x%x 0x%x 0x%x\n",
|
|
data_arg.offset, pdata->code_offset, pdata->code_allowed_size);
|
|
err = -EFAULT;
|
|
return err;
|
|
}
|
|
if (data_arg.size > pdata->code_allowed_size) {
|
|
gif_err("size too big to download:0x%x 0x%x\n",
|
|
data_arg.size, pdata->code_allowed_size);
|
|
err = -EFAULT;
|
|
return err;
|
|
}
|
|
|
|
gif_info("Configuration Data OFFSET: 0x%08x SIZE: 0x%08x\n",
|
|
data_arg.offset, data_arg.size);
|
|
return gnss_load_data(iod, data_arg);
|
|
}
|
|
|
|
static int gnss_read_firmware(struct io_device *iod,
|
|
struct kepler_firmware_args firmware_arg)
|
|
{
|
|
struct link_device *ld = iod->ld;
|
|
int err = 0;
|
|
|
|
gif_debug("Read Firmware - fw size : %d, fw_offset : %d\n",
|
|
firmware_arg.firmware_size, firmware_arg.offset);
|
|
|
|
if (!ld->copy_reserved_to_user) {
|
|
gif_err("No copy_reserved_to_user method\n");
|
|
err = -EFAULT;
|
|
goto read_firmware_exit;
|
|
}
|
|
|
|
err = ld->copy_reserved_to_user(iod->ld, firmware_arg.offset,
|
|
firmware_arg.firmware_bin, firmware_arg.firmware_size);
|
|
if (err) {
|
|
gif_err("Unable to read firmware\n");
|
|
err = -EFAULT;
|
|
goto read_firmware_exit;
|
|
}
|
|
|
|
read_firmware_exit:
|
|
return err;
|
|
}
|
|
|
|
static int parsing_read_firmware(struct io_device *iod, unsigned long arg)
|
|
{
|
|
struct kepler_firmware_args firmware_arg;
|
|
int err = 0;
|
|
|
|
memset(&firmware_arg, 0, sizeof(struct kepler_firmware_args));
|
|
err = copy_from_user(&firmware_arg, (const void __user *)arg,
|
|
sizeof(struct kepler_firmware_args));
|
|
if (err) {
|
|
gif_err("copy_from_user fail(to get structure)\n");
|
|
err = -EFAULT;
|
|
return err;
|
|
}
|
|
|
|
return gnss_read_firmware(iod, firmware_arg);
|
|
}
|
|
|
|
static int change_tcxo_mode(struct gnss_ctl *gc, unsigned long arg)
|
|
{
|
|
enum gnss_tcxo_mode tcxo_mode;
|
|
int ret;
|
|
|
|
ret = copy_from_user(&tcxo_mode, (const void __user *)arg,
|
|
sizeof(enum gnss_tcxo_mode));
|
|
if (ret) {
|
|
gif_err("copy_from_user fail(to get tcxo mode)\n");
|
|
ret = -EFAULT;
|
|
goto change_mode_exit;
|
|
}
|
|
|
|
if (!gc->pmu_ops->change_tcxo_mode) {
|
|
gif_err("func is null\n");
|
|
ret = -EFAULT;
|
|
goto change_mode_exit;
|
|
}
|
|
|
|
ret = gc->pmu_ops->change_tcxo_mode(tcxo_mode);
|
|
|
|
change_mode_exit:
|
|
return ret;
|
|
}
|
|
|
|
static int set_sensor_power(struct gnss_ctl *gc, unsigned long arg)
|
|
{
|
|
enum sensor_power sensor_power_en;
|
|
int ret;
|
|
|
|
ret = copy_from_user(&sensor_power_en, (const void __user *)arg,
|
|
sizeof(enum sensor_power));
|
|
if (ret) {
|
|
gif_err("copy_from_user fail(to get sensor power setting)\n");
|
|
ret = -EFAULT;
|
|
goto set_sensor_power_exit;
|
|
}
|
|
|
|
if (!gc->ops.set_sensor_power) {
|
|
gif_err("func is null\n");
|
|
ret = -EFAULT;
|
|
goto set_sensor_power_exit;
|
|
}
|
|
|
|
ret = gc->ops.set_sensor_power(gc, sensor_power_en);
|
|
|
|
set_sensor_power_exit:
|
|
return ret;
|
|
}
|
|
|
|
static long misc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct io_device *iod = (struct io_device *)filp->private_data;
|
|
struct link_device *ld = iod->ld;
|
|
struct gnss_ctl *gc = iod->gc;
|
|
struct gnss_swreg swreg;
|
|
struct gnss_apreg apreg;
|
|
int err = 0;
|
|
int size;
|
|
int ret = 0;
|
|
|
|
if (!valid_cmd_arg(cmd, arg))
|
|
return -ENOTTY;
|
|
|
|
switch (cmd) {
|
|
case GNSS_IOCTL_RESET:
|
|
gif_info("%s: GNSS_IOCTL_RESET\n", iod->name);
|
|
|
|
if (!gc->ops.gnss_hold_reset) {
|
|
gif_err("%s: !gc->ops.gnss_reset\n", iod->name);
|
|
return -EINVAL;
|
|
}
|
|
ret = gc->ops.gnss_hold_reset(gc);
|
|
skb_queue_purge(&iod->sk_rx_q);
|
|
return ret;
|
|
|
|
case GNSS_IOCTL_REQ_FAULT_INFO:
|
|
gif_info("%s: GNSS_IOCTL_REQ_FAULT_INFO\n", iod->name);
|
|
|
|
if (!gc->ops.gnss_req_fault_info) {
|
|
gif_err("%s: !gc->ops.req_fault_info\n", iod->name);
|
|
return -EFAULT;
|
|
}
|
|
size = gc->ops.gnss_req_fault_info(gc);
|
|
|
|
gif_info("gnss_req_fault_info returned %d\n", size);
|
|
|
|
if (size < 0) {
|
|
gif_err("Can't get fault info from Kepler\n");
|
|
return ret;
|
|
}
|
|
|
|
if (size > 0) {
|
|
err = ld->dump_fault_to_user(ld, (void __user *)arg, size);
|
|
if (err) {
|
|
gif_err("copy_to_user fail(to copy fault info)\n");
|
|
return -EFAULT;
|
|
}
|
|
}
|
|
return size;
|
|
|
|
case GNSS_IOCTL_REQ_BCMD:
|
|
gif_info("%s: GNSS_IOCTL_REQ_BCMD\n", iod->name);
|
|
return send_bcmd(iod, arg);
|
|
|
|
case GNSS_IOCTL_LOAD_FIRMWARE:
|
|
gif_info("%s: GNSS_IOCTL_LOAD_FIRMWARE\n", iod->name);
|
|
return parsing_load_firmware(iod, arg);
|
|
|
|
case GNSS_IOCTL_LOAD_DATA:
|
|
gif_info("%s: GNSS_IOCTL_LOAD_DATA\n", iod->name);
|
|
return parsing_load_data(iod, arg);
|
|
|
|
case GNSS_IOCTL_READ_FIRMWARE:
|
|
gif_debug("%s: GNSS_IOCTL_READ_FIRMWARE\n", iod->name);
|
|
return parsing_read_firmware(iod, arg);
|
|
|
|
case GNSS_IOCTL_CHANGE_SENSOR_GPIO:
|
|
gif_info("%s: GNSS_IOCTL_CHANGE_SENSOR_GPIO\n", iod->name);
|
|
|
|
if (!gc->ops.change_sensor_gpio) {
|
|
gif_err("%s: !gc->ops.change_sensor_gpio\n", iod->name);
|
|
return -EFAULT;
|
|
}
|
|
return gc->ops.change_sensor_gpio(gc);
|
|
|
|
case GNSS_IOCTL_CHANGE_TCXO_MODE:
|
|
gif_info("%s: GNSS_IOCTL_CHANGE_TCXO_MODE\n", iod->name);
|
|
return change_tcxo_mode(gc, arg);
|
|
|
|
case GNSS_IOCTL_SET_SENSOR_POWER:
|
|
gif_info("%s: GNSS_IOCTL_SENSOR_POWER\n", iod->name);
|
|
return set_sensor_power(gc, arg);
|
|
|
|
case GNSS_IOCTL_SET_WATCHDOG_RESET:
|
|
gif_info("%s: GNSS_IOCTL_SET_WATCHDOG_RESET\n", iod->name);
|
|
#if IS_ENABLED(CONFIG_DEBUG_SNAPSHOT)
|
|
return dbg_snapshot_expire_watchdog();
|
|
#else
|
|
gif_err("debug snapshot is not enabled\n");
|
|
return -EINVAL;
|
|
#endif
|
|
|
|
case GNSS_IOCTL_READ_SHMEM_SIZE:
|
|
gif_info("%s: GNSS_IOCTL_READ_SHMEM_SIZE\n", iod->name);
|
|
return gc->pdata->shmem_size;
|
|
|
|
case GNSS_IOCTL_READ_RESET_COUNT:
|
|
gif_info("%s: GNSS_IOCTL_READ_RESET_COUNT\n", iod->name);
|
|
return gc->reset_count;
|
|
|
|
case GNSS_IOCTL_GET_SWREG:
|
|
gif_info("%s: GNSS_IOCTL_GET_SWREG\n", iod->name);
|
|
if (!gc->pmu_ops->get_swreg) {
|
|
gif_err("get_swreg is not available\n");
|
|
return -EINVAL;
|
|
}
|
|
gc->pmu_ops->get_swreg(&swreg);
|
|
err = copy_to_user((void __user *)arg, &swreg, sizeof(struct gnss_swreg));
|
|
if (err)
|
|
gif_err("copy to user fail for swreg (0x%08x)\n", err);
|
|
|
|
return err;
|
|
|
|
case GNSS_IOCTL_GET_APREG:
|
|
gif_info("%s: GNSS_IOCTL_GET_APREG\n", iod->name);
|
|
if (!gc->pmu_ops->get_apreg) {
|
|
gif_err("get_apreg is not available\n");
|
|
return -EINVAL;
|
|
}
|
|
gc->pmu_ops->get_apreg(&apreg);
|
|
err = copy_to_user((void __user *)arg, &apreg, sizeof(struct gnss_apreg));
|
|
if (err)
|
|
gif_err("copy to user fail for apreg (0x%08x)\n", err);
|
|
|
|
return err;
|
|
|
|
case GNSS_IOCTL_RELEASE_RESET:
|
|
gif_info("%s: GNSS_IOCTL_RELEASE_RESET\n", iod->name);
|
|
|
|
if (!gc->ops.gnss_release_reset) {
|
|
gif_err("%s: !gc->ops.gnss_release_reset\n", iod->name);
|
|
return -EINVAL;
|
|
}
|
|
ret = gc->ops.gnss_release_reset(gc);
|
|
return ret;
|
|
|
|
case GNSS_IOCTL_POWER_ON:
|
|
gif_info("%s: GNSS_IOCTL_POWER_ON\n", iod->name);
|
|
|
|
if (!gc->ops.gnss_power_on) {
|
|
gif_err("%s: !gc->ops.gnss_power_on\n", iod->name);
|
|
return -EINVAL;
|
|
}
|
|
ret = gc->ops.gnss_power_on(gc);
|
|
return ret;
|
|
|
|
default:
|
|
gif_err("%s: ERR! undefined cmd 0x%X\n", iod->name, cmd);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_COMPAT)
|
|
static int parsing_load_firmware32(struct io_device *iod, unsigned long arg)
|
|
{
|
|
struct kepler_firmware_args firmware_arg;
|
|
struct kepler_firmware_args32 arg32;
|
|
int err = 0;
|
|
|
|
memset(&firmware_arg, 0, sizeof(firmware_arg));
|
|
err = copy_from_user(&arg32, (const void __user *)arg,
|
|
sizeof(struct kepler_firmware_args32));
|
|
if (err) {
|
|
gif_err("copy_from_user fail(to get structure)\n");
|
|
err = -EFAULT;
|
|
|
|
return err;
|
|
}
|
|
|
|
firmware_arg.firmware_size = arg32.firmware_size;
|
|
firmware_arg.offset = arg32.offset;
|
|
firmware_arg.firmware_bin = compat_ptr(arg32.firmware_bin);
|
|
|
|
return gnss_load_firmware(iod, firmware_arg);
|
|
}
|
|
|
|
static int parsing_read_firmware32(struct io_device *iod, unsigned long arg)
|
|
{
|
|
struct kepler_firmware_args firmware_arg;
|
|
struct kepler_firmware_args32 arg32;
|
|
int err = 0;
|
|
|
|
memset(&firmware_arg, 0, sizeof(firmware_arg));
|
|
err = copy_from_user(&arg32, (const void __user *)arg,
|
|
sizeof(struct kepler_firmware_args32));
|
|
if (err) {
|
|
gif_err("copy_from_user fail(to get structure)\n");
|
|
err = -EFAULT;
|
|
|
|
return err;
|
|
}
|
|
|
|
firmware_arg.firmware_size = arg32.firmware_size;
|
|
firmware_arg.offset = arg32.offset;
|
|
firmware_arg.firmware_bin = compat_ptr(arg32.firmware_bin);
|
|
|
|
return gnss_read_firmware(iod, firmware_arg);
|
|
}
|
|
|
|
static long misc_compat_ioctl(struct file *filp,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct io_device *iod = (struct io_device *)filp->private_data;
|
|
unsigned long realarg = (unsigned long)compat_ptr(arg);
|
|
|
|
if (!valid_cmd_arg(cmd, realarg))
|
|
return -ENOTTY;
|
|
|
|
switch (cmd) {
|
|
case GNSS_IOCTL_LOAD_FIRMWARE:
|
|
gif_info("%s: GNSS_IOCTL_LOAD_FIRMWARE (32-bit)\n", iod->name);
|
|
return parsing_load_firmware32(iod, realarg);
|
|
case GNSS_IOCTL_READ_FIRMWARE:
|
|
gif_info("%s: GNSS_IOCTL_READ_FIRMWARE (32-bit)\n", iod->name);
|
|
return parsing_read_firmware32(iod, realarg);
|
|
}
|
|
return misc_ioctl(filp, cmd, realarg);
|
|
}
|
|
#endif
|
|
|
|
static void exynos_build_header(struct io_device *iod, struct link_device *ld,
|
|
u8 *buff, u16 cfg, u8 ctl, size_t count)
|
|
{
|
|
u16 *exynos_header = (u16 *)(buff + EXYNOS_START_OFFSET);
|
|
u16 *frame_seq = (u16 *)(buff + EXYNOS_FRAME_SEQ_OFFSET);
|
|
u16 *frag_cfg = (u16 *)(buff + EXYNOS_FRAG_CONFIG_OFFSET);
|
|
u16 *size = (u16 *)(buff + EXYNOS_LEN_OFFSET);
|
|
struct exynos_seq_num *seq_num = &(iod->seq_num);
|
|
|
|
*exynos_header = EXYNOS_START_MASK;
|
|
*frame_seq = ++seq_num->frame_cnt;
|
|
*frag_cfg = cfg;
|
|
*size = (u16)(EXYNOS_HEADER_SIZE + count);
|
|
buff[EXYNOS_CH_ID_OFFSET] = 0; /* single channel, should be 0. */
|
|
|
|
if (cfg == EXYNOS_SINGLE_MASK)
|
|
*frag_cfg = cfg;
|
|
|
|
buff[EXYNOS_CH_SEQ_OFFSET] = ++seq_num->ch_cnt[0];
|
|
}
|
|
|
|
static ssize_t misc_write(struct file *filp, const char __user *data,
|
|
size_t count, loff_t *fpos)
|
|
{
|
|
struct io_device *iod = (struct io_device *)filp->private_data;
|
|
struct link_device *ld = iod->ld;
|
|
struct sk_buff *skb;
|
|
u8 *buff;
|
|
int ret;
|
|
size_t headroom;
|
|
size_t tailroom;
|
|
size_t tx_bytes;
|
|
u16 fr_cfg;
|
|
struct gnss_ctl *gc = iod->gc;
|
|
|
|
if (gc->gnss_state != STATE_ONLINE) {
|
|
gif_err_limited("%s: ERR! gnss is not online\n", iod->name);
|
|
return SIGHUP;
|
|
}
|
|
|
|
fr_cfg = EXYNOS_SINGLE_MASK << 8;
|
|
headroom = EXYNOS_HEADER_SIZE;
|
|
tailroom = exynos_calc_padding_size(EXYNOS_HEADER_SIZE + count);
|
|
|
|
tx_bytes = headroom + count + tailroom;
|
|
|
|
skb = alloc_skb(tx_bytes, GFP_KERNEL);
|
|
if (!skb) {
|
|
gif_err("%s: ERR! alloc_skb fail (tx_bytes:%ld)\n",
|
|
iod->name, tx_bytes);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Store the IO device, the link device, etc. */
|
|
skbpriv(skb)->iod = iod;
|
|
skbpriv(skb)->ld = ld;
|
|
|
|
skbpriv(skb)->lnk_hdr = iod->link_header;
|
|
skbpriv(skb)->exynos_ch = 0; /* Single channel should be 0. */
|
|
|
|
/* Build EXYNOS link header */
|
|
if (fr_cfg) {
|
|
buff = skb_put(skb, headroom);
|
|
exynos_build_header(iod, ld, buff, fr_cfg, 0, count);
|
|
}
|
|
|
|
/* Store IPC message */
|
|
buff = skb_put(skb, count);
|
|
if (copy_from_user(buff, data, count)) {
|
|
gif_err("%s->%s: ERR! copy_from_user fail (count %ld)\n",
|
|
iod->name, ld->name, count);
|
|
dev_kfree_skb_any(skb);
|
|
return -EFAULT;
|
|
}
|
|
|
|
/* Apply padding */
|
|
if (tailroom)
|
|
skb_put(skb, tailroom);
|
|
|
|
/* send data with sk_buff, link device will put sk_buff
|
|
* into the specific sk_buff_q and run work-q to send data
|
|
*/
|
|
skbpriv(skb)->iod = iod;
|
|
skbpriv(skb)->ld = ld;
|
|
|
|
ret = ld->send(ld, iod, skb);
|
|
if (ret < 0) {
|
|
gif_err("%s->%s: ERR! ld->send fail (err %d, tx_bytes %ld)\n",
|
|
iod->name, ld->name, ret, tx_bytes);
|
|
return ret;
|
|
}
|
|
|
|
if (ret != tx_bytes) {
|
|
gif_debug("%s->%s: WARNING! ret %d != tx_bytes %ld (count %ld)\n",
|
|
iod->name, ld->name, ret, tx_bytes, count);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t misc_read(struct file *filp, char *buf, size_t count,
|
|
loff_t *fpos)
|
|
{
|
|
struct io_device *iod = (struct io_device *)filp->private_data;
|
|
struct sk_buff_head *rxq = &iod->sk_rx_q;
|
|
struct sk_buff *skb;
|
|
int copied = 0;
|
|
struct gnss_ctl *gc = iod->gc;
|
|
|
|
#if IS_ENABLED(CONFIG_USB_CONFIGFS_F_MBIM)
|
|
if (gc->is_irq_received == true) {
|
|
gc->is_irq_received = false;
|
|
if (copy_to_user(buf, &gc->gnss_pwr, sizeof(unsigned int))) {
|
|
gif_err("%s: ERR! copy_to_user fail\n", iod->name);
|
|
return -EFAULT;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
if (gc->gnss_state != STATE_ONLINE) {
|
|
gif_err("%s: ERR! gnss is not online\n", iod->name);
|
|
return SIGHUP;
|
|
}
|
|
|
|
if (skb_queue_empty(rxq)) {
|
|
gif_debug("%s: ERR! no data in rxq\n", iod->name);
|
|
return 0;
|
|
}
|
|
|
|
skb = skb_dequeue(rxq);
|
|
if (unlikely(!skb)) {
|
|
gif_debug("%s: No data in RXQ\n", iod->name);
|
|
return 0;
|
|
}
|
|
|
|
copied = skb->len > count ? count : skb->len;
|
|
|
|
if (copy_to_user(buf, skb->data, copied)) {
|
|
gif_err("%s: ERR! copy_to_user fail\n", iod->name);
|
|
dev_kfree_skb_any(skb);
|
|
return -EFAULT;
|
|
}
|
|
|
|
gif_debug("%s: data:%d copied:%d qlen:%d\n",
|
|
iod->name, skb->len, copied, rxq->qlen);
|
|
|
|
if (skb->len > count) {
|
|
skb_pull(skb, count);
|
|
skb_queue_head(rxq, skb);
|
|
} else {
|
|
dev_kfree_skb_any(skb);
|
|
}
|
|
|
|
return copied;
|
|
}
|
|
|
|
static const struct file_operations misc_io_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = misc_open,
|
|
.release = misc_release,
|
|
.poll = misc_poll,
|
|
.unlocked_ioctl = misc_ioctl,
|
|
#if IS_ENABLED(CONFIG_COMPAT)
|
|
.compat_ioctl = misc_compat_ioctl,
|
|
#endif
|
|
.write = misc_write,
|
|
.read = misc_read,
|
|
};
|
|
|
|
int exynos_init_gnss_io_device(struct io_device *iod, struct device *dev)
|
|
{
|
|
int ret = 0;
|
|
|
|
/* Matt - GNSS uses link headers; placeholder code */
|
|
iod->link_header = true;
|
|
|
|
/* Get data from link device */
|
|
gif_info("%s: init\n", iod->name);
|
|
iod->recv_skb = io_dev_recv_skb_from_link_dev;
|
|
iod->recv_skb_single = io_dev_recv_skb_single_from_link_dev;
|
|
|
|
/* Register misc device */
|
|
init_waitqueue_head(&iod->wq);
|
|
skb_queue_head_init(&iod->sk_rx_q);
|
|
|
|
iod->miscdev.minor = MISC_DYNAMIC_MINOR;
|
|
iod->miscdev.name = iod->name;
|
|
iod->miscdev.fops = &misc_io_fops;
|
|
iod->waketime = WAKE_TIME;
|
|
iod->ws = gnssif_wake_lock_register(dev, iod->name);
|
|
if (iod->ws == NULL) {
|
|
gif_err("%s: wakeup_source_register fail\n", iod->name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = misc_register(&iod->miscdev);
|
|
if (ret)
|
|
gif_err("%s: ERR! misc_register failed\n", iod->name);
|
|
|
|
return ret;
|
|
}
|