root/net/caif/cfsrvl.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) ST-Ericsson AB 2010
 * Author:      Sjur Brendeland
 */

#define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__

#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/pkt_sched.h>
#include <net/caif/caif_layer.h>
#include <net/caif/cfsrvl.h>
#include <net/caif/cfpkt.h>
#include <net/caif/caif_dev.h>

#define SRVL_CTRL_PKT_SIZE 1
#define SRVL_FLOW_OFF 0x81
#define SRVL_FLOW_ON  0x80
#define SRVL_SET_PIN  0x82

#define container_obj(layr) container_of(layr, struct cfsrvl, layer)

static void cfservl_ctrlcmd(struct cflayer *layr, enum caif_ctrlcmd ctrl,
                            int phyid)
{
        struct cfsrvl *service = container_obj(layr);

        if (layr->up == NULL || layr->up->ctrlcmd == NULL)
                return;

        switch (ctrl) {
        case CAIF_CTRLCMD_INIT_RSP:
                service->open = true;
                layr->up->ctrlcmd(layr->up, ctrl, phyid);
                break;
        case CAIF_CTRLCMD_DEINIT_RSP:
        case CAIF_CTRLCMD_INIT_FAIL_RSP:
                service->open = false;
                layr->up->ctrlcmd(layr->up, ctrl, phyid);
                break;
        case _CAIF_CTRLCMD_PHYIF_FLOW_OFF_IND:
                if (phyid != service->dev_info.id)
                        break;
                if (service->modem_flow_on)
                        layr->up->ctrlcmd(layr->up,
                                          CAIF_CTRLCMD_FLOW_OFF_IND, phyid);
                service->phy_flow_on = false;
                break;
        case _CAIF_CTRLCMD_PHYIF_FLOW_ON_IND:
                if (phyid != service->dev_info.id)
                        return;
                if (service->modem_flow_on) {
                        layr->up->ctrlcmd(layr->up,
                                           CAIF_CTRLCMD_FLOW_ON_IND,
                                           phyid);
                }
                service->phy_flow_on = true;
                break;
        case CAIF_CTRLCMD_FLOW_OFF_IND:
                if (service->phy_flow_on) {
                        layr->up->ctrlcmd(layr->up,
                                          CAIF_CTRLCMD_FLOW_OFF_IND, phyid);
                }
                service->modem_flow_on = false;
                break;
        case CAIF_CTRLCMD_FLOW_ON_IND:
                if (service->phy_flow_on) {
                        layr->up->ctrlcmd(layr->up,
                                          CAIF_CTRLCMD_FLOW_ON_IND, phyid);
                }
                service->modem_flow_on = true;
                break;
        case _CAIF_CTRLCMD_PHYIF_DOWN_IND:
                /* In case interface is down, let's fake a remove shutdown */
                layr->up->ctrlcmd(layr->up,
                                CAIF_CTRLCMD_REMOTE_SHUTDOWN_IND, phyid);
                break;
        case CAIF_CTRLCMD_REMOTE_SHUTDOWN_IND:
                layr->up->ctrlcmd(layr->up, ctrl, phyid);
                break;
        default:
                pr_warn("Unexpected ctrl in cfsrvl (%d)\n", ctrl);
                /* We have both modem and phy flow on, send flow on */
                layr->up->ctrlcmd(layr->up, ctrl, phyid);
                service->phy_flow_on = true;
                break;
        }
}

static int cfservl_modemcmd(struct cflayer *layr, enum caif_modemcmd ctrl)
{
        struct cfsrvl *service = container_obj(layr);

        caif_assert(layr != NULL);
        caif_assert(layr->dn != NULL);
        caif_assert(layr->dn->transmit != NULL);

        if (!service->supports_flowctrl)
                return 0;

        switch (ctrl) {
        case CAIF_MODEMCMD_FLOW_ON_REQ:
                {
                        struct cfpkt *pkt;
                        struct caif_payload_info *info;
                        u8 flow_on = SRVL_FLOW_ON;
                        pkt = cfpkt_create(SRVL_CTRL_PKT_SIZE);
                        if (!pkt)
                                return -ENOMEM;

                        if (cfpkt_add_head(pkt, &flow_on, 1) < 0) {
                                pr_err("Packet is erroneous!\n");
                                cfpkt_destroy(pkt);
                                return -EPROTO;
                        }
                        info = cfpkt_info(pkt);
                        info->channel_id = service->layer.id;
                        info->hdr_len = 1;
                        info->dev_info = &service->dev_info;
                        cfpkt_set_prio(pkt, TC_PRIO_CONTROL);
                        return layr->dn->transmit(layr->dn, pkt);
                }
        case CAIF_MODEMCMD_FLOW_OFF_REQ:
                {
                        struct cfpkt *pkt;
                        struct caif_payload_info *info;
                        u8 flow_off = SRVL_FLOW_OFF;
                        pkt = cfpkt_create(SRVL_CTRL_PKT_SIZE);
                        if (!pkt)
                                return -ENOMEM;

                        if (cfpkt_add_head(pkt, &flow_off, 1) < 0) {
                                pr_err("Packet is erroneous!\n");
                                cfpkt_destroy(pkt);
                                return -EPROTO;
                        }
                        info = cfpkt_info(pkt);
                        info->channel_id = service->layer.id;
                        info->hdr_len = 1;
                        info->dev_info = &service->dev_info;
                        cfpkt_set_prio(pkt, TC_PRIO_CONTROL);
                        return layr->dn->transmit(layr->dn, pkt);
                }
        default:
          break;
        }
        return -EINVAL;
}

static void cfsrvl_release(struct cflayer *layer)
{
        struct cfsrvl *service = container_of(layer, struct cfsrvl, layer);
        kfree(service);
}

void cfsrvl_init(struct cfsrvl *service,
                 u8 channel_id,
                 struct dev_info *dev_info,
                 bool supports_flowctrl)
{
        caif_assert(offsetof(struct cfsrvl, layer) == 0);
        service->open = false;
        service->modem_flow_on = true;
        service->phy_flow_on = true;
        service->layer.id = channel_id;
        service->layer.ctrlcmd = cfservl_ctrlcmd;
        service->layer.modemcmd = cfservl_modemcmd;
        service->dev_info = *dev_info;
        service->supports_flowctrl = supports_flowctrl;
        service->release = cfsrvl_release;
}

bool cfsrvl_ready(struct cfsrvl *service, int *err)
{
        if (!service->open) {
                *err = -ENOTCONN;
                return false;
        }
        return true;
}

bool cfsrvl_phyid_match(struct cflayer *layer, int phyid)
{
        struct cfsrvl *servl = container_obj(layer);
        return servl->dev_info.id == phyid;
}

void caif_free_client(struct cflayer *adap_layer)
{
        struct cfsrvl *servl;
        if (adap_layer == NULL || adap_layer->dn == NULL)
                return;
        servl = container_obj(adap_layer->dn);
        servl->release(&servl->layer);
}
EXPORT_SYMBOL(caif_free_client);

void caif_client_register_refcnt(struct cflayer *adapt_layer,
                                 void (*hold)(struct cflayer *lyr),
                                 void (*put)(struct cflayer *lyr))
{
        struct cfsrvl *service;

        if (WARN_ON(adapt_layer == NULL || adapt_layer->dn == NULL))
                return;
        service = container_of(adapt_layer->dn, struct cfsrvl, layer);
        service->hold = hold;
        service->put = put;
}
EXPORT_SYMBOL(caif_client_register_refcnt);