blob: 183a8bd07e1d1d77d1e7e5c521070483a0ced0c5 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* linux/ipc/shm_ctrl.c
* Copyright (C) 1992, 1993 Krishna Balasubramanian
* Replaced `struct shm_desc' by `struct vm_area_struct', July 1994.
* Fixed the shm swap deallocation (shm_unuse()), August 1998 Andrea Arcangeli.
*
* /proc/sysvipc/shm support (c) 1999 Dragos Acostachioaie <dragos@iname.com>
* Make shmmax, shmall, shmmni sysctl'able, Christoph Rohland <cr@sap.com>
* Move the mm functionality over to mm/shmem.c, Christoph Rohland <cr@sap.com>
*
* Better ipc lock (kern_ipc_perm.lock) handling
* Davidlohr Bueso <davidlohr.bueso@hp.com>, June 2013.
*/
#include <linux/mm.h>
#include <asm/pgtable.h>
#include "shm_ctrl.h"
#include "../mm/internal.h"
/**
* ºê¶¨Òå
*/
#define SHM_UNIT_BUFF_ORDER (12)
#define SHM_KEYS_STATUS_LEN (4*1024)
#define SHM_REMOTE_BUFF_LEN (128*1024)
#define SHM_POSIX_HASH_CHARS (26)
#define SHM_POSIX_HASH_BASE (62)
#define SHM_POSIX_HASH_MASK (0x7FF)
#define SHM_BUFF_BASE_PHY_ADDR (g_shm_phyAddr)
#define SHM_UNIT_BUFF_SIZE (1UL<<SHM_UNIT_BUFF_ORDER) /*4KB*/
#define SHM_UNIT_INDEX(addr) (((unsigned long)addr - SHM_BUFF_BASE_PHY_ADDR) >> SHM_UNIT_BUFF_ORDER)
#define SHM_UNIT_PAGE_ADDR(index) ((void *)(SHM_BUFF_BASE_PHY_ADDR + ((unsigned long)index << SHM_UNIT_BUFF_ORDER)))
#define SHM_UNIT_NUM_BITS (SHM_REMOTE_BUFF_LEN >> SHM_UNIT_BUFF_ORDER)
#define SHM_CTRL_BITMAP_NUM (SHM_UNIT_NUM_BITS / SHM_CTRL_LONG_32BIT)
struct shm_key_node {
key_t key;
unsigned int vma_count;
DECLARE_BITMAP(shm_inuse_index, SHM_UNIT_NUM_BITS);
};
struct shm_entity {
DECLARE_BITMAP(shm_regions_bitmap, SHM_UNIT_NUM_BITS);/*×ÜÄÚ´æ³Ø¹ÜÀíÐÅÏ¢*/
struct shm_key_node keys_info_head[SHM_UNIT_NUM_BITS]; /*ÿ¸öshmµÄkey¹ÜÀíÐÅÏ¢*/
};
/**
* È«¾Ö±äÁ¿¶¨Òå
*/
phys_addr_t g_shm_phyAddr = 0;
void *g_shm_region = NULL;
struct shm_entity *shm_remote_manager;
/*******************************************************************************
* ¹¦ÄÜÃèÊö: shm_hash_name_to_key
* ²ÎÊý˵Ã÷:
* (´«Èë²ÎÊý) name: ¹²ÏíÄÚ´æÃû³Æ
* (´«Èë²ÎÊý) len: ¹²ÏíÄÚ´æÃû³Æ³¤¶È
* (´«³ö²ÎÊý) ÎÞ
* ·µ »Ø Öµ:
* ÆäËü˵Ã÷: This function is used for calc hash value of the name(-2048~-4096)
*******************************************************************************/
key_t shm_hash_name_to_key(const char *name, int len)
{
int i = 0;
key_t tmp_key = 0;
key_t hash_key = 0;
unsigned long long id = 0;
for (; i < len; i++)
{
if (name[i] >= 'A' && name[i] <= 'Z')
id = id*SHM_POSIX_HASH_BASE + name[i]-'A';
else if (name[i] >= 'a' && name[i] <= 'z')
id = id*SHM_POSIX_HASH_BASE + name[i]-'a' + SHM_POSIX_HASH_CHARS;
else if (name[i] >= '0' && name[i] <= '9')
id = id*SHM_POSIX_HASH_BASE + name[i]-'0' + 2*SHM_POSIX_HASH_CHARS;
}
tmp_key =(id & SHM_POSIX_HASH_MASK) + (SHM_POSIX_HASH_MASK + 1);
hash_key = ~tmp_key + 1;
return hash_key;
}
/*******************************************************************************
* ¹¦ÄÜÃèÊö: shm_quary_keyArray
* ²ÎÊý˵Ã÷:
* (´«Èë²ÎÊý) void
* (´«³ö²ÎÊý) void
* ·µ »Ø Öµ: SHM_CTRL_OK or SHM_CTRL_ERROR
* ÆäËü˵Ã÷: This function is used for search a special key in array
*******************************************************************************/
static int shm_quary_keyArray(const key_t key)
{
unsigned int index = 0;
struct shm_key_node *shm_data = NULL;
shm_data = shm_remote_manager->keys_info_head;
for (; index < SHM_UNIT_NUM_BITS; index++)
{
if (shm_data[index].key == key)
return index;
}
return SHM_CTRL_ERROR;
}
/*******************************************************************************
* ¹¦ÄÜÃèÊö: shm_ctrl_pte_range
* ²ÎÊý˵Ã÷:
* (´«Èë²ÎÊý) void
* (´«³ö²ÎÊý) void
* ·µ »Ø Öµ: SHM_CTRL_OK or SHM_CTRL_ERROR
* ÆäËü˵Ã÷: This function is used for clear the pagetable pte
*******************************************************************************/
unsigned long shm_ctrl_pte_range(struct mm_struct *mm,
struct vm_area_struct *vma, pmd_t *pmd,
unsigned long addr, unsigned long end)
{
spinlock_t *ptl;
pte_t *start_pte;
pte_t *pte;
start_pte = pte_offset_map_lock(mm, pmd, addr, &ptl);
pte = start_pte;
arch_enter_lazy_mmu_mode();
do {
pte_t ptent = *pte;
if (pte_none(ptent)) {
continue;
}
if (pte_present(ptent)) {
ptent = ptep_get_and_clear(mm, addr, pte);
}
pte_clear_not_present_full(mm, addr, pte, 0);
} while (pte++, addr += PAGE_SIZE, addr != end);
arch_leave_lazy_mmu_mode();
pte_unmap_unlock(start_pte, ptl);
return addr;
}
/*******************************************************************************
* ¹¦ÄÜÃèÊö: shm_ctrl_pmd_range
* ²ÎÊý˵Ã÷:
* (´«Èë²ÎÊý) mm: ÈÎÎñµÄÄÚ´æÃèÊö·û
* (´«Èë²ÎÊý) vma£º¿çºË¹²ÏíÄÚ´æ½ø³ÌµØÖ·¿Õ¼ävma
* (´«Èë²ÎÊý) pud£ºpudÒ³ÉϲãĿ¼
* (´«Èë²ÎÊý) addr: ÐéÄâÆðʼµØÖ·
* (´«Èë²ÎÊý) end: ÐéÄâ½áÊøµØÖ·
* (´«³ö²ÎÊý) ¿Õ
* ·µ »Ø Öµ: addr
* ÆäËü˵Ã÷: This function is used for clear the pagetable pte
*******************************************************************************/
static inline unsigned long shm_ctrl_pmd_range(struct mm_struct *mm,
struct vm_area_struct *vma, pud_t *pud,
unsigned long addr, unsigned long end)
{
pmd_t *pmd;
unsigned long next;
pmd = pmd_offset(pud, addr);
do {
next = pmd_addr_end(addr, end);
/*
* Here there can be other concurrent MADV_DONTNEED or
* trans huge page faults running, and if the pmd is
* none or trans huge it can change under us. This is
* because MADV_DONTNEED holds the mmap_sem in read
* mode.
*/
if (pmd_none_or_trans_huge_or_clear_bad(pmd))
goto next;
next = shm_ctrl_pte_range(mm, vma, pmd, addr, next);
next:
cond_resched();
} while (pmd++, addr = next, addr != end);
return addr;
}
/*******************************************************************************
* ¹¦ÄÜÃèÊö: shm_ctrl_pud_range
* ²ÎÊý˵Ã÷:
* (´«Èë²ÎÊý) mm: ÈÎÎñµÄÄÚ´æÃèÊö·û
* (´«Èë²ÎÊý) vma: ¿çºË¹²ÏíÄÚ´æ½ø³ÌµØÖ·¿Õ¼ävma
* (´«Èë²ÎÊý) pgd: pgdҳĿ¼Ïî
* (´«Èë²ÎÊý) addr: ÐéÄâÆðʼµØÖ·
* (´«Èë²ÎÊý) end: ÐéÄâ½áÊøµØÖ·
* (´«³ö²ÎÊý) ÎÞ
* ·µ »Ø Öµ: SHM_CTRL_OK or SHM_CTRL_ERROR
* ÆäËü˵Ã÷: This function is used for find pud
*******************************************************************************/
static inline unsigned long shm_ctrl_pud_range(struct mm_struct *mm,
struct vm_area_struct *vma, pgd_t *pgd,
unsigned long addr, unsigned long end)
{
pud_t *pud;
unsigned long next;
pud = pud_offset(pgd, addr);
do {
next = pud_addr_end(addr, end);
if (pud_none_or_clear_bad(pud))
continue;
next = shm_ctrl_pmd_range(mm, vma, pud, addr, next);
} while (pud++, addr = next, addr != end);
return addr;
}
/*******************************************************************************
* ¹¦ÄÜÃèÊö: shm_unmap_page_range
* ²ÎÊý˵Ã÷:
* (´«Èë²ÎÊý) mm: ÈÎÎñµÄÄÚ´æÃèÊö·û
* (´«Èë²ÎÊý) vma ¿çºË¹²ÏíÄÚ´æ½ø³ÌµØÖ·¿Õ¼ävma
* (´«Èë²ÎÊý) addr ÐéÄâÆðʼµØÖ·
* (´«Èë²ÎÊý) end ÐéÄâ½áÊøµØÖ·
* (´«³ö²ÎÊý) ¿Õ
* ·µ »Ø Öµ: void
* ÆäËü˵Ã÷: This function is used for unmap the shm memory
*******************************************************************************/
void shm_unmap_page_range(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long addr, unsigned long end)
{
pgd_t *pgd;
unsigned long next;
BUG_ON(addr >= end);
pgd = pgd_offset(vma->vm_mm, addr);
do {
next = pgd_addr_end(addr, end);
if (pgd_none_or_clear_bad(pgd))
continue;
next = shm_ctrl_pud_range(mm, vma, pgd, addr, next);
} while (pgd++, addr = next, addr != end);
}
/*******************************************************************************
* ¹¦ÄÜÃèÊö: shm_vma_write_pagetable
* ²ÎÊý˵Ã÷:
* (´«Èë²ÎÊý) void
* (´«³ö²ÎÊý) void
* ·µ »Ø Öµ: SHM_CTRL_OK or SHM_CTRL_ERROR
* ÆäËü˵Ã÷: This function is used for create pagetable for shm mem region
*******************************************************************************/
static int shm_vma_write_pagetable(struct vm_area_struct *vma, unsigned long vm_addr,
phys_addr_t shmaddr_phy)
{
pte_t *pte;
int retval = 0;
pte_t pte_val = 0;
spinlock_t *ptl;
if (vm_addr < vma->vm_start || vm_addr >= vma->vm_end)
return -EFAULT;
pte = get_locked_pte(vma->vm_mm, vm_addr, &ptl);
if ((!pte) || (!pte_none(*pte)))
return -EFAULT;
pte_val = __pte((phys_addr_t)(shmaddr_phy) | pgprot_val(vma->vm_page_prot));
set_pte_at(vma->vm_mm, vm_addr, pte, pte_val);
pte_unmap_unlock(pte, ptl);
return retval;
}
/*******************************************************************************
* ¹¦ÄÜÃèÊö: shm_fill_keytable
* ²ÎÊý˵Ã÷:
* (´«Èë²ÎÊý) void
* (´«³ö²ÎÊý) void
* ·µ »Ø Öµ: SHM_CTRL_OK or SHM_CTRL_ERROR
* ÆäËü˵Ã÷: This function is used for record the key and index relation
*******************************************************************************/
int shm_fill_keytable(struct shm_key_node *keydata)
{
unsigned int key_index = 0;
if (keydata == NULL)
return SHM_CTRL_ERROR;
for(; key_index < SHM_UNIT_NUM_BITS; key_index++)
{
if(shm_remote_manager->keys_info_head[key_index].key == 0)
{
memcpy(&shm_remote_manager->keys_info_head[key_index], keydata, sizeof(struct shm_key_node));
return SHM_CTRL_OK;
}
}
return SHM_CTRL_ERROR;
}
/*******************************************************************************
* ¹¦ÄÜÃèÊö: shm_remove_keynode
* ²ÎÊý˵Ã÷:
* (´«Èë²ÎÊý) void
* (´«³ö²ÎÊý) void
* ·µ »Ø Öµ: SHM_CTRL_OK or SHM_CTRL_ERROR
* ÆäËü˵Ã÷: This function is used for remove the key and index relation
*******************************************************************************/
static void shm_remove_keynode(unsigned int key_index)
{
memset(&shm_remote_manager->keys_info_head[key_index], 0, sizeof(struct shm_key_node));
}
/*******************************************************************************
* ¹¦ÄÜÃèÊö: shm_alloc_new_page
* ²ÎÊý˵Ã÷:
* (´«Èë²ÎÊý) void
* (´«³ö²ÎÊý) void
* ·µ »Ø Öµ: SHM_CTRL_OK or SHM_CTRL_ERROR
* ÆäËü˵Ã÷: This function is used for alloc page from shm mem region
*******************************************************************************/
int shm_alloc_new_page(struct vm_area_struct *vma, key_t key)
{
unsigned long vm_addr = 0;
unsigned int region_index = 0;
void *new_page = NULL;
struct shm_key_node new_key = {0};
if((vma == NULL) || (g_shm_region == NULL))
{
printk("Shm region is not ready\n");
return SHM_CTRL_ERROR;
}
vm_addr = vma->vm_start;
for (; vm_addr < vma->vm_end; vm_addr += PAGE_SIZE)
{
region_index = find_first_zero_bit(shm_remote_manager->shm_regions_bitmap, SHM_UNIT_NUM_BITS);
if (region_index < SHM_UNIT_NUM_BITS)
{
set_bit(region_index, shm_remote_manager->shm_regions_bitmap);
new_page = SHM_UNIT_PAGE_ADDR(region_index);
if (shm_vma_write_pagetable(vma, vm_addr, new_page))
{
return SHM_CTRL_ERROR;
}
set_bit(region_index, new_key.shm_inuse_index);
}
else
{
return SHM_CTRL_ERROR;
}
}
if (!bitmap_empty(new_key.shm_inuse_index, SHM_UNIT_NUM_BITS))
{
new_key.key = key;
new_key.vma_count++;
shm_fill_keytable(&new_key);
}
return SHM_CTRL_OK;
}
/*******************************************************************************
* ¹¦ÄÜÃèÊö: shm_do_newseg_check
* ²ÎÊý˵Ã÷:
* (´«Èë²ÎÊý) void
* (´«³ö²ÎÊý) void
* ·µ »Ø Öµ: SHM_CTRL_OK or SHM_CTRL_ERROR
* ÆäËü˵Ã÷: This function is used for check key and len
*******************************************************************************/
int shm_do_newseg_check(key_t key, unsigned long len)
{
int ret = 0;
int key_index = 0;
unsigned int shm_weight = 0;
unsigned int shm_pages = 0;
struct shm_key_node *key_node = NULL;
if(g_shm_region == NULL)
{
printk("shm_do_newsg_check:Shm region is not ready\n");
return SHM_CTRL_ERROR;
}
soft_spin_lock(SHM_SFLOCK);
key_index = shm_quary_keyArray(key);
if (key_index < 0)
{
soft_spin_unlock(SHM_SFLOCK);
return SHM_CTRL_OK;
}
if ((0 <= key_index) && (key_index < SHM_UNIT_NUM_BITS))
{
key_node = &shm_remote_manager->keys_info_head[key_index];
}
else
{
soft_spin_unlock(SHM_SFLOCK);
panic("key_index out of range: failed\n");
}
shm_pages = PAGE_ALIGN(len) >> PAGE_SHIFT;
shm_weight = bitmap_weight(key_node->shm_inuse_index, SHM_UNIT_NUM_BITS);
soft_spin_unlock(SHM_SFLOCK);
/*APºÍCAP¹²ÏíÄÚ´æ´óСӦƥÅä*/
if(shm_weight != shm_pages)
return -EINVAL;
else
return SHM_CTRL_OK;
}
/*******************************************************************************
* ¹¦ÄÜÃèÊö: shm_do_remote_map_vma
* ²ÎÊý˵Ã÷:
* (´«Èë²ÎÊý) void
* (´«³ö²ÎÊý) void
* ·µ »Ø Öµ: SHM_CTRL_OK or SHM_CTRL_ERROR
* ÆäËü˵Ã÷: This function is used for
*
/*²éѯkey,Èç¹ûÒÑ·ÖÅä¹ýʹÓÃkey¶ÔÓ¦µÄbitmap, ·ñÔò´Ó×ÜÄÚ´æ³Ø·ÖÅä
*******************************************************************************/
int shm_do_remote_map_vma(struct vm_area_struct *vma, key_t key)
{
int ret = 0;
unsigned long vm_addr = 0;
unsigned int region_index = 0;
int key_index = 0;
void *new_page_phy = NULL;
struct shm_key_node *key_node = NULL;
DECLARE_BITMAP(shm_inuse_tmp, SHM_UNIT_NUM_BITS);
if((vma == NULL) || (g_shm_region == NULL))
{
printk("shm_do_remote_map_vma:Shm region is not ready\n");
return SHM_CTRL_ERROR;
}
/*Ó³ÉävmaΪ·ÇcacheÊôÐÔ*/
pgprot_noncached(vma->vm_page_prot);
soft_spin_lock(SHM_SFLOCK);
key_index = shm_quary_keyArray(key);
if (key_index < 0)
{
ret = shm_alloc_new_page(vma, key);
soft_spin_unlock(SHM_SFLOCK);
if (ret < 0)
panic("shm_alloc_new_page Fail\n");
return ret;
}
vm_addr = vma->vm_start;
if ((0 <= key_index) && (key_index < SHM_UNIT_NUM_BITS))
{
key_node = &shm_remote_manager->keys_info_head[key_index];
}
else
{
soft_spin_unlock(SHM_SFLOCK);
panic("key_index out of range: failed\n");
}
memcpy(shm_inuse_tmp, key_node->shm_inuse_index, sizeof(shm_inuse_tmp));
for (; vm_addr < vma->vm_end; vm_addr += PAGE_SIZE)
{
region_index = find_first_bit(shm_inuse_tmp, SHM_UNIT_NUM_BITS);
if (region_index < SHM_UNIT_NUM_BITS)
{
new_page_phy = SHM_UNIT_PAGE_ADDR(region_index);
if (shm_vma_write_pagetable(vma, vm_addr, new_page_phy))
{
soft_spin_unlock(SHM_SFLOCK);
panic("shm_do_remote_map_vma vm_insert_page failed\n");
return SHM_CTRL_ERROR;
}
clear_bit(region_index, shm_inuse_tmp);
}
else
{
return SHM_CTRL_ERROR;
}
}
key_node->vma_count++;
soft_spin_unlock(SHM_SFLOCK);
return SHM_CTRL_OK;
}
/*******************************************************************************
* ¹¦ÄÜÃèÊö: shm_remote_free_pages
* ²ÎÊý˵Ã÷:
* (´«Èë²ÎÊý) void
* (´«³ö²ÎÊý) void
* ·µ »Ø Öµ: SHM_CTRL_OK or SHM_CTRL_ERROR
* ÆäËü˵Ã÷: This function is used for
*******************************************************************************/
int shm_remote_free_pages(key_t key)
{
int key_index = 0;
unsigned int region_index = 0;
struct shm_key_node *key_node = NULL;
if(g_shm_region == NULL)
{
printk("shm_remote_free_pages: Shm region is not ready\n");
return SHM_CTRL_ERROR;
}
soft_spin_lock(SHM_SFLOCK);
/*²éѯkey*/
key_index = shm_quary_keyArray(key);
if(key_index < 0 || key_index >= SHM_UNIT_NUM_BITS)
{
soft_spin_unlock(SHM_SFLOCK);
panic("error\n");
}
/*ÊôÓÚ¿çºËµÄ¾ÍÊͷŵ½³Ø×ÓÀï*/
key_node = &shm_remote_manager->keys_info_head[key_index];
key_node->vma_count--;
if(key_node->vma_count == 0)
{
for_each_set_bit(region_index, key_node->shm_inuse_index, SHM_UNIT_NUM_BITS)
{
clear_bit(region_index, shm_remote_manager->shm_regions_bitmap);
}
shm_remove_keynode(key_index);
}
soft_spin_unlock(SHM_SFLOCK);
return SHM_CTRL_OK;
}
/*******************************************************************************
* ¹¦ÄÜÃèÊö: shm_rpcore_init
* ²ÎÊý˵Ã÷:
* (´«Èë²ÎÊý) void
* (´«³ö²ÎÊý) void
* ·µ »Ø Öµ:
* ÆäËü˵Ã÷: This function is used for shm ctrl init
*******************************************************************************/
static int __init shm_rpcore_init(void)
{
dma_addr_t dma_phys;
dma_addr_t shm_keyInfo_phys;
struct shm_pool_msg shm_msg = {0};
g_shm_region = dma_alloc_coherent(NULL,
(size_t)SHM_REMOTE_BUFF_LEN,
&dma_phys,
GFP_KERNEL);
if(!g_shm_region)
{
panic("g_shm_region NOMEM\n");
}
g_shm_phyAddr = dma_phys;
shm_remote_manager = dma_alloc_coherent(NULL,
(size_t)(SHM_KEYS_STATUS_LEN),
&shm_keyInfo_phys,
GFP_KERNEL);
if(!shm_remote_manager)
{
panic("shm_remote_manager NOMEM\n");
}
memset(shm_remote_manager, 0, sizeof(struct shm_entity));
shm_msg.shm_len = SHM_REMOTE_BUFF_LEN;
shm_msg.key_manage_len = SHM_KEYS_STATUS_LEN;
shm_msg.shm_memory_phy = g_shm_phyAddr;
shm_msg.key_manage_phy = shm_keyInfo_phys;
memcpy((void*)IRAM_BASE_ADDR_SHM_REMOTE_REGION, &shm_msg, sizeof(struct shm_pool_msg));
return SHM_CTRL_OK;
}
late_initcall(shm_rpcore_init);