/** @file link_device_memory_main.c @brief common functions for all types of memory interface media @date 2014/02/05 @author Hankook Jang (hankook.jang@samsung.com) */ /* * Copyright (C) 2011 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 #include "modem_v1.h" #include "modem_prj.h" #include "modem_utils.h" #include "link_device_memory.h" #include "include/sbd.h" #include #ifdef GROUP_MEM_LINK_SBD /** @weakgroup group_mem_link_sbd @{ */ #ifdef GROUP_MEM_LINK_SETUP /** @weakgroup group_mem_link_setup @{ */ static void print_sbd_config(struct sbd_link_device *sl) { #ifdef DEBUG_MODEM_IF int i; pr_err("mif: SBD_IPC {shmem_base:0x%pK shmem_size:%d}\n", sl->shmem, sl->shmem_size); pr_err("mif: SBD_IPC {version:%d num_channels:%d rbps_offset:%d}\n", sl->g_desc->version, sl->g_desc->num_channels, sl->g_desc->rbps_offset); for (i = 0; i < sl->num_channels; i++) { struct sbd_rb_channel *rb_ch = &sl->g_desc->rb_ch[i]; struct sbd_rb_desc *rbd; rbd = &sl->g_desc->rb_desc[i][UL]; pr_err("mif: RB_DESC[%-2d][UL](offset:%d) = " "{id:%-2d ch:%-3d dir:%s} " "{sbdv_offset:%-5d rb_len:%-3d} " "{buff_size:%-4d payload_offset:%d}\n", i, rb_ch->ul_rbd_offset, rbd->id, rbd->ch, udl_str(rbd->direction), rb_ch->ul_sbdv_offset, rbd->length, rbd->buff_size, rbd->payload_offset); rbd = &sl->g_desc->rb_desc[i][DL]; pr_err("mif: RB_DESC[%-2d][DL](offset:%d) = " "{id:%-2d ch:%-3d dir:%s} " "{sbdv_offset:%-5d rb_len:%-3d} " "{buff_size:%d payload_offset:%d}\n", i, rb_ch->dl_rbd_offset, rbd->id, rbd->ch, udl_str(rbd->direction), rb_ch->dl_sbdv_offset, rbd->length, rbd->buff_size, rbd->payload_offset); } #endif } static void init_desc_alloc(struct sbd_link_device *sl, unsigned int offset) { sl->desc_alloc_offset = offset; } static void *desc_alloc(struct sbd_link_device *sl, size_t size) { u8 *desc = (sl->shmem + sl->desc_alloc_offset); sl->desc_alloc_offset += size; return desc; } static void init_buff_alloc(struct sbd_link_device *sl, unsigned int offset) { sl->buff_alloc_offset = offset; } static u8 *buff_alloc(struct sbd_link_device *sl, unsigned int size) { u8 *buff = (sl->shmem + sl->buff_alloc_offset); sl->buff_alloc_offset += size; return buff; } /** @brief set up an SBD RB descriptor in SHMEM */ static void setup_sbd_rb_desc(struct sbd_rb_desc *rb_desc, struct sbd_ring_buffer *rb) { rb_desc->ch = rb->ch; rb_desc->direction = rb->dir; rb_desc->signaling = 1; rb_desc->sig_mask = MASK_INT_VALID | MASK_SEND_DATA; rb_desc->length = rb->len; rb_desc->id = rb->id; rb_desc->buff_size = rb->buff_size; rb_desc->payload_offset = rb->payload_offset; } /** @brief set up an SBD RB (1) build an SBD RB instance in the kernel space\n (2) allocate an SBD array in SHMEM\n (3) allocate a data buffer array in SHMEM if possible\n */ static int setup_sbd_rb(struct sbd_link_device *sl, struct sbd_ring_buffer *rb, enum direction dir, struct sbd_link_attr *link_attr) { size_t alloc_size; unsigned int i; rb->sl = sl; rb->lnk_hdr = link_attr->lnk_hdr; rb->zerocopy = link_attr->zerocopy; rb->more = false; rb->total = 0; rb->rcvd = 0; /* Initialize an SBD RB instance in the kernel space. */ rb->id = link_attr->id; rb->ch = link_attr->ch ?: SIPC_CH_ID_PDP_0; rb->dir = dir; rb->len = link_attr->rb_len[dir]; rb->buff_size = link_attr->buff_size[dir]; rb->payload_offset = 0; /* Prepare array of pointers to the data buffer for each SBD */ alloc_size = (rb->len * sizeof(u8 *)); if (!rb->buff) rb->buff = kmalloc(alloc_size, GFP_ATOMIC); if (!rb->buff) return -ENOMEM; /* (1) Allocate an array of data buffers in SHMEM. (2) Register the address of each data buffer. */ alloc_size = ((u32)rb->len * (u32)rb->buff_size); rb->buff_rgn = (u8 *)buff_alloc(sl, alloc_size); if (!rb->buff_rgn) return -ENOMEM; for (i = 0; i < rb->len; i++) rb->buff[i] = rb->buff_rgn + (i * rb->buff_size); mif_err("RB[%d:%d][%s] buff_rgn {addr:0x%pK offset:%d size:%lu}\n", rb->id, rb->ch, udl_str(dir), rb->buff_rgn, calc_offset(rb->buff_rgn, sl->shmem), alloc_size); #ifdef CONFIG_SBD_BOOTLOG if (rb->buff_rgn + alloc_size >= sl->shmem + sl->shmem_size - SHMEM_BOOTSBDLOG_SIZE) { mif_err("sbd buffer break boot log area\n"); return -ENOMEM; } #endif /* Prepare SBD array in SHMEM. */ rb->rp = &sl->rp[rb->dir][rb->id]; rb->wp = &sl->wp[rb->dir][rb->id]; alloc_size = (rb->len * sizeof(u32)); rb->addr_v = (u32 *)desc_alloc(sl, alloc_size); if (!rb->addr_v) return -ENOMEM; rb->size_v = (u32 *)desc_alloc(sl, alloc_size); if (!rb->size_v) return -ENOMEM; /* Register each data buffer to the corresponding SBD. */ for (i = 0; i < rb->len; i++) { rb->addr_v[i] = calc_offset(rb->buff[i], sl->shmem); rb->size_v[i] = 0; } rb->iod = link_get_iod_with_channel(sl->ld, rb->ch); rb->ld = sl->ld; return 0; } static void setup_desc_rgn(struct sbd_link_device *sl) { size_t size; #if 1 mif_err("SHMEM {base:0x%pK size:%d}\n", sl->shmem, sl->shmem_size); #endif /* Allocate @g_desc. */ size = sizeof(struct sbd_global_desc); sl->g_desc = (struct sbd_global_desc *)desc_alloc(sl, size); #if 1 mif_err("G_DESC_OFFSET = %d(0x%pK)\n", calc_offset(sl->g_desc, sl->shmem), sl->g_desc); mif_err("RB_CH_OFFSET = %d (0x%pK)\n", calc_offset(sl->g_desc->rb_ch, sl->shmem), sl->g_desc->rb_ch); mif_err("RBD_PAIR_OFFSET = %d (0x%pK)\n", calc_offset(sl->g_desc->rb_desc, sl->shmem), sl->g_desc->rb_desc); #endif size = sizeof(u16) * ULDL * RDWR * sl->num_channels; sl->rbps = (u16 *)desc_alloc(sl, size); #if 1 mif_err("RBP_SET_OFFSET = %d (0x%pK)\n", calc_offset(sl->rbps, sl->shmem), sl->rbps); #endif /* Set up @g_desc. */ sl->g_desc->version = sl->version; sl->g_desc->num_channels = sl->num_channels; sl->g_desc->rbps_offset = calc_offset(sl->rbps, sl->shmem); /* Set up pointers to each RBP array. */ sl->rp[UL] = sl->rbps + sl->num_channels * 0; sl->wp[UL] = sl->rbps + sl->num_channels * 1; sl->rp[DL] = sl->rbps + sl->num_channels * 2; sl->wp[DL] = sl->rbps + sl->num_channels * 3; #if 1 mif_err("Complete!!\n"); #endif } static void setup_link_attr(struct sbd_link_attr *link_attr, u16 id, u16 ch, struct modem_io_t *io_dev) { link_attr->id = id; link_attr->ch = ch; if (io_dev->attrs & IODEV_ATTR(ATTR_NO_LINK_HEADER)) link_attr->lnk_hdr = false; else link_attr->lnk_hdr = true; link_attr->rb_len[UL] = io_dev->ul_num_buffers; link_attr->buff_size[UL] = io_dev->ul_buffer_size; link_attr->rb_len[DL] = io_dev->dl_num_buffers; link_attr->buff_size[DL] = io_dev->dl_buffer_size; #if defined(CONFIG_CP_ZEROCOPY) if (io_dev->attrs & IODEV_ATTR(ATTR_ZEROCOPY)) link_attr->zerocopy = true; else link_attr->zerocopy = false; #endif } static int init_sbd_ipc(struct sbd_link_device *sl, struct sbd_ipc_device ipc_dev[], struct sbd_link_attr link_attr[]) { int i; setup_desc_rgn(sl); for (i = 0; i < sl->num_channels; i++) { struct sbd_rb_channel *rb_ch = &sl->g_desc->rb_ch[i]; struct sbd_rb_desc *rb_desc; struct sbd_ring_buffer *rb; int ret; ipc_dev[i].id = link_attr[i].id; ipc_dev[i].ch = link_attr[i].ch; ipc_dev[i].zerocopy = link_attr[i].zerocopy; /* Setup UL Ring Buffer in the ipc_dev[$i] */ rb = &ipc_dev[i].rb[UL]; ret = setup_sbd_rb(sl, rb, UL, &link_attr[i]); if (ret < 0) return ret; /* Setup UL RB_DESC & UL RB_CH in the g_desc */ rb_desc = &sl->g_desc->rb_desc[i][UL]; setup_sbd_rb_desc(rb_desc, rb); rb_ch->ul_rbd_offset = calc_offset(rb_desc, sl->shmem); rb_ch->ul_sbdv_offset = calc_offset(rb->addr_v, sl->shmem); /* Setup DL Ring Buffer in the ipc_dev[$i] */ rb = &ipc_dev[i].rb[DL]; ret = setup_sbd_rb(sl, rb, DL, &link_attr[i]); if (ret < 0) return ret; /* Setup DL RB_DESC & DL RB_CH in the g_desc */ rb_desc = &sl->g_desc->rb_desc[i][DL]; setup_sbd_rb_desc(rb_desc, rb); rb_ch->dl_rbd_offset = calc_offset(rb_desc, sl->shmem); rb_ch->dl_sbdv_offset = calc_offset(rb->addr_v, sl->shmem); #ifdef CONFIG_CP_ZEROCOPY /* Setup zerocopy_adaptor if zerocopy ipc_dev */ ret = setup_zerocopy_adaptor(&ipc_dev[i]); if (ret < 0) return ret; #endif } return 0; } static void init_ipc_device(struct sbd_link_device *sl, u16 id, struct sbd_ipc_device *ipc_dev) { u16 ch = sbd_id2ch(sl, id); struct sbd_ring_buffer *rb; ipc_dev->id = id; ipc_dev->ch = ch; atomic_set(&ipc_dev->config_done, 0); rb = &ipc_dev->rb[UL]; spin_lock_init(&rb->lock); skb_queue_head_init(&rb->skb_q); atomic_set(&rb->busy, 0); rb = &ipc_dev->rb[DL]; spin_lock_init(&rb->lock); skb_queue_head_init(&rb->skb_q); atomic_set(&rb->busy, 0); } /** @return the number of actual link channels */ static unsigned int init_ctrl_tables(struct sbd_link_device *sl, int num_iodevs, struct modem_io_t iodevs[]) { int i; unsigned int id; unsigned int qos_prio = QOS_HIPRIO; /* Fill ch2id array with MAX_LINK_CHANNELS value to prevent sbd_ch2id() from returning 0 for unused channels. */ for (i = 0; i < MAX_SIPC_CHANNELS; i++) sl->ch2id[i] = MAX_LINK_CHANNELS; for (id = 0, i = 0; i < num_iodevs; i++) { int ch = iodevs[i].id; if ((sipc5_ipc_ch(ch) && !sipc_ps_ch(ch)) || iodevs[i].format == IPC_MULTI_RAW) { /* Skip making rb if mismatch region info */ if (iodevs[i].attrs & IODEV_ATTR(ATTR_OPTION_REGION) && strncmp(iodevs[i].option_region, CONFIG_OPTION_REGION, strlen(iodevs[i].option_region))) continue; /* Change channel to Qos priority */ if (iodevs[i].format == IPC_MULTI_RAW) ch = qos_prio++; /* Save CH# to LinkID-to-CH conversion table. */ sl->id2ch[id] = ch; /* Save LinkID to CH-to-LinkID conversion table. */ sl->ch2id[ch] = id; /* Set up the attribute table entry of a LinkID. */ setup_link_attr(&sl->link_attr[id], id, ch, &iodevs[i]); ++id; } } #ifndef CONFIG_MODEM_IF_QOS for (i = 0; i < num_iodevs; i++) { int ch = iodevs[i].id; if (sipc_ps_ch(ch)) sl->ch2id[ch] = sl->ch2id[QOS_HIPRIO]; } #endif /* Finally, id has the number of actual link channels. */ return id; } int init_sbd_link(struct sbd_link_device *sl) { int err; if (!sl) return -ENOMEM; memset(sl->shmem + DESC_RGN_OFFSET, 0, DESC_RGN_SIZE); init_desc_alloc(sl, DESC_RGN_OFFSET); init_buff_alloc(sl, BUFF_RGN_OFFSET); err = init_sbd_ipc(sl, sl->ipc_dev, sl->link_attr); if (!err) print_sbd_config(sl); return err; } int create_sbd_link_device(struct link_device *ld, struct sbd_link_device *sl, u8 *shmem_base, unsigned int shmem_size) { int i; int num_iodevs; struct modem_io_t *iodevs; if (!ld || !sl || !shmem_base) return -EINVAL; if (!ld->mdm_data) return -EINVAL; num_iodevs = ld->mdm_data->num_iodevs; iodevs = ld->mdm_data->iodevs; sl->ld = ld; sl->version = 1; sl->shmem = shmem_base; sl->shmem_size = shmem_size; sl->num_channels = init_ctrl_tables(sl, num_iodevs, iodevs); for (i = 0; i < sl->num_channels; i++) init_ipc_device(sl, i, sbd_id2dev(sl, i)); sl->reset_zerocopy_done = 1; return 0; } /** @} */ #endif #ifdef GROUP_MEM_IPC_TX /** @weakgroup group_mem_ipc_tx @{ */ /** @brief check the free space in a SBD RB @param rb the pointer to an SBD RB instance @retval "> 0" the size of free space in the @b @@dev TXQ @retval "< 0" an error code */ static inline int check_rb_space(struct sbd_ring_buffer *rb, unsigned int qlen, unsigned int in, unsigned int out) { unsigned int space; if (!circ_valid(qlen, in, out)) { mif_err("ERR! TXQ[%d:%d] DIRTY (qlen:%d in:%d out:%d)\n", rb->id, rb->ch, qlen, in, out); return -EIO; } space = circ_get_space(qlen, in, out); if (unlikely(space < 1)) { mif_err_limited("TXQ[%d:%d] NOSPC (qlen:%d in:%d out:%d)\n", rb->id, rb->ch, qlen, in, out); return -ENOSPC; } return space; } int sbd_pio_tx(struct sbd_ring_buffer *rb, struct sk_buff *skb) { int ret; unsigned int qlen = rb->len; unsigned int in = *rb->wp; unsigned int out = *rb->rp; unsigned int count = skb->len; unsigned int space = (rb->buff_size - rb->payload_offset); u8 *dst; ret = check_rb_space(rb, qlen, in, out); if (unlikely(ret < 0)) return ret; if (unlikely(count > space)) { mif_err("ERR! {id:%d ch:%d} count %d > space %d\n", rb->id, rb->ch, count, space); return -ENOSPC; } barrier(); dst = rb->buff[in] + rb->payload_offset; barrier(); skb_copy_from_linear_data(skb, dst, count); if (sipc_ps_ch(rb->ch)) { struct io_device *iod = skbpriv(skb)->iod; unsigned int ch = iod->id; rb->size_v[in] = (skb->len & 0xFFFF); rb->size_v[in] |= (ch << 16); } else { rb->size_v[in] = skb->len; } barrier(); *rb->wp = circ_new_ptr(qlen, in, 1); /* Commit the item before incrementing the head */ smp_mb(); return count; } /** @} */ #endif #ifdef GROUP_MEM_IPC_RX /** @weakgroup group_mem_ipc_rx @{ */ static inline struct sk_buff *recv_data(struct sbd_ring_buffer *rb, u16 out) { struct sk_buff *skb; u8 *src; unsigned int len = rb->size_v[out] & 0xFFFF; unsigned int space = (rb->buff_size - rb->payload_offset); if (unlikely(len > space)) { mif_err("ERR! {id:%d ch:%d} size %d > space %d\n", rb->id, rb->ch, len, space); return NULL; } skb = dev_alloc_skb(len); if (unlikely(!skb)) { mif_err("ERR! {id:%d ch:%d} alloc_skb(%d) fail\n", rb->id, rb->ch, len); return NULL; } src = rb->buff[out] + rb->payload_offset; skb_put(skb, len); skb_copy_to_linear_data(skb, src, len); return skb; } static inline void set_skb_priv(struct sbd_ring_buffer *rb, struct sk_buff *skb) { unsigned int out = *rb->rp; /* Record the IO device, the link device, etc. into &skb->cb */ if (sipc_ps_ch(rb->ch)) { unsigned ch = (rb->size_v[out] >> 16) & 0xffff; skbpriv(skb)->iod = link_get_iod_with_channel(rb->ld, ch); skbpriv(skb)->ld = rb->ld; skbpriv(skb)->sipc_ch = ch; } else { skbpriv(skb)->iod = rb->iod; skbpriv(skb)->ld = rb->ld; skbpriv(skb)->sipc_ch = rb->ch; } } struct sk_buff *sbd_pio_rx(struct sbd_ring_buffer *rb) { struct sk_buff *skb; unsigned int qlen = rb->len; unsigned int out = *rb->rp; skb = recv_data(rb, out); if (unlikely(!skb)) return NULL; set_lnk_hdr(rb, skb); set_skb_priv(rb, skb); check_more(rb, skb); *rb->rp = circ_new_ptr(qlen, out, 1); return skb; } /** @} */ #endif /** // End of group_mem_link_sbd @} */ #endif