You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

442 lines
10 KiB

/*
* Copyright (c) Hisilicon Technologies Co., Ltd. 2008-2021. All rights reserved.
* Description: Generic misc open routine.
* Author: Hisilicon
* Create: 2008-01-01
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/delay.h>
#include "drv_media_ext.h"
#include "media_base.h"
#define mkstr(exp) #exp
#define mkmarcotostr(exp) mkstr(exp)
#define VERSION_STRING ("SDK_VERSION:[" mkmarcotostr(SDK_VERSION) "] Build Time:[" __DATE__ ", " __TIME__ "]")
/*
* Head entry for the doubly linked media_device list
*/
static LIST_HEAD(g_media_list);
static DEFINE_MUTEX(g_media_sem);
/*
* Assigned numbers, used for dynamic minors
*/
#define DYNAMIC_MINORS 128 /* like dynamic majors */
static unsigned char g_media_minors[DYNAMIC_MINORS / 8]; /* 8 表示以8为粒度 */
#ifdef CONFIG_PROC_FS
static void *media_seq_start(struct seq_file *seq, loff_t *pos)
{
mutex_lock(&g_media_sem);
return seq_list_start(&g_media_list, *pos);
}
static void *media_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
return seq_list_next(v, &g_media_list, pos);
}
static void media_seq_stop(struct seq_file *seq, void *v)
{
mutex_unlock(&g_media_sem);
}
static int media_seq_show(struct seq_file *seq, void *v)
{
const pm_device *p = list_entry(v, pm_device, list);
seq_printf(seq, "%3i %s\n", p->minor, (char *)p->name ? (char *)p->name : "");
return 0;
}
static struct seq_operations g_media_seq_ops = {
.start = media_seq_start,
.next = media_seq_next,
.stop = media_seq_stop,
.show = media_seq_show,
};
static int media_seq_open(struct inode *inode, struct file *file)
{
return seq_open(file, &g_media_seq_ops);
}
#if (LINUX_VERSION_CODE >=KERNEL_VERSION(5, 10, 0))
static struct proc_ops g_media_proc_fops = {
.proc_open = media_seq_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = seq_release,
};
#else
static struct file_operations g_media_proc_fops = {
.owner = THIS_MODULE,
.open = media_seq_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
#endif
#endif
static int media_open(struct inode *inode, struct file *file)
{
td_u32 minor = iminor(inode);
pm_device *c = NULL;
int err = -1;
const struct file_operations *old_fops = NULL;
const struct file_operations *new_fops = NULL;
mutex_lock(&g_media_sem);
list_for_each_entry(c, &g_media_list, list)
{
if (c->minor == minor) {
new_fops = fops_get(c->app_ops);
break;
}
}
mutex_unlock(&g_media_sem);
if (new_fops == NULL) {
goto fail;
}
err = 0;
old_fops = file->f_op;
file->f_op = new_fops;
if (file->f_op->open) {
file->private_data = c->dev;
err = file->f_op->open(inode, file);
if (err) {
if (file->f_op != NULL && file->f_op->owner != NULL) {
fops_put(file->f_op);
}
if (old_fops != NULL && old_fops->owner != NULL) {
file->f_op = fops_get(old_fops);
}
}
}
if (old_fops != NULL && old_fops->owner != NULL) {
fops_put(old_fops);
}
fail:
return err;
}
static struct class *g_media_class;
static struct file_operations g_media_fops = {
.owner = THIS_MODULE,
.open = media_open,
};
/**
* ext_drv_pm_register - register a media device
* @media: device structure
*
* Register a media device with the kernel. If the minor
* number is set to %EXT_DRV_MEDIA_DYNAMIC_MINOR a minor number is assigned
* and placed in the minor field of the structure. For other cases
* the minor number requested is used.
*
* The structure passed is linked into the kernel and may not be
* destroyed until it has been unregistered.
*
* A zero is returned on success and a negative errno code for
* failure.
*/
static int pm_register_get_media_minor(pm_device *media, pm_device **pdev)
{
pm_device *c = NULL;
int i = DYNAMIC_MINORS;
if (media == NULL) {
return -EINVAL;
}
list_for_each_entry(c, &g_media_list, list) {
if (c->minor == media->minor) {
return -EBUSY;
}
if (c->minor == media->parent_minor) {
*pdev = c;
}
}
if (media->minor == EXT_DRV_MEDIA_DYNAMIC_MINOR) {
while (--i >= 0) {
if (((td_u32)i >> 0x3) >= (DYNAMIC_MINORS / 8)) { /* 8 表示以8为粒度 */
continue;
}
if ((g_media_minors[(td_u32)i >> 0x3] & (1 << ((td_u32)i & 0x7))) == 0) {
break;
}
}
if (i < 0) {
return -EBUSY;
}
media->minor = (td_u32)i;
}
if (media->minor < DYNAMIC_MINORS) {
g_media_minors[media->minor >> 0x3] |= 1 << (media->minor & 0x7);
}
return TD_SUCCESS;
}
static int pm_register_add_media_device(const pm_device *media, pm_basedev **bdev, pm_device *pdev)
{
int ret;
if (bdev == NULL) {
return TD_FAILURE;
}
*bdev = media_device_alloc(media->name, -1);
if (*bdev == NULL) {
return -ENOMEM;
}
if (pdev != NULL) {
(*bdev)->dev.parent = pdev->app_device;
pr_info("PM_RESUME:devices:%s parent minor:%d", media->name, media->parent_minor);
}
ret = media_device_add(*bdev);
if (ret) {
media_device_put(*bdev);
return ret;
}
return TD_SUCCESS;
}
static int pm_register_device_create(pm_device *media,
pm_basedev *bdev, dev_t *dev, struct device **adev)
{
int ret;
*dev = MKDEV(EXT_DRV_MEDIA_DEVICE_MAJOR, media->minor);
*adev = device_create(g_media_class, &(bdev->dev), *dev, NULL,
"%s", media->name);
if (IS_ERR(*adev)) {
ret = PTR_ERR(*adev);
return ret;
}
return TD_SUCCESS;
}
static int pm_register_device_register(pm_device *media, struct device *adev, pm_basedev *bdev)
{
pm_basedrv *bdrv = NULL;
int ret;
bdrv = media_driver_alloc(media->name, media->owner, media->base_ops);
if (bdrv == NULL) {
return -ENOMEM;
}
ret = media_driver_register(bdrv);
if (ret) {
media_driver_release(bdrv);
return ret;
}
media->app_device = adev;
media->base_device = bdev;
media->base_driver = bdrv;
return TD_SUCCESS;
}
int drv_pm_register(pm_device *media)
{
int ret;
dev_t dev;
struct device *adev = NULL;
pm_basedev *bdev = NULL;
pm_device *pdev = NULL;
if ((media == NULL) || (media->name == NULL) || (media->app_ops == NULL)) {
return -EINVAL;
}
#ifdef MODULE
if (media->owner == NULL) {
return -EINVAL;
}
#endif
mutex_lock(&g_media_sem);
/* 1 */
ret = pm_register_get_media_minor(media, &pdev);
if (ret != TD_SUCCESS) {
goto out;
}
/* 2 base device, then class is NULL */
ret = pm_register_add_media_device(media, &bdev, pdev);
if (ret != TD_SUCCESS) {
goto err0;
}
/* 3 app class */
ret = pm_register_device_create(media, bdev, &dev, &adev);
if (ret != TD_SUCCESS) {
goto err1;
}
/* 4 base driver */
ret = pm_register_device_register(media, adev, bdev);
if (ret != TD_SUCCESS) {
goto err2;
}
/*
* Add it to the front, so that later devices can "override"
* earlier defaults
*/
INIT_LIST_HEAD(&media->list);
list_add(&media->list, &g_media_list);
goto out;
err2:
device_destroy(g_media_class, dev);
err1:
ext_media_device_unregister(bdev);
err0:
g_media_minors[media->minor >> 3] &= ~(1 << (media->minor & 7)); /* 3 1 7 表示偏移位数 */
out:
mutex_unlock(&g_media_sem);
return ret;
}
/**
* drv_pm_un_register - unregister a media device
* @media: device to unregister
*
* Unregister a media device that was previously
* successfully registered with ext_drv_pm_register(). Success
* is indicated by a zero return, a negative errno code
* indicates an error.
*/
int drv_pm_un_register(pm_device *media)
{
unsigned int i;
if (g_media_class == NULL || media == NULL || (media->name == NULL) || (media->app_ops == NULL)) {
return -EINVAL;
}
#ifdef MODULE
if (media->owner == NULL) {
return -EINVAL;
}
#endif
i = media->minor;
if (list_empty(&media->list)) {
return -EINVAL;
}
if ((i >= DYNAMIC_MINORS) || (i < 0)) {
return -EINVAL;
}
mutex_lock(&g_media_sem);
list_del(&media->list);
if (media->base_driver) {
media_driver_unregister(media->base_driver);
media_driver_release(media->base_driver);
media->base_driver = NULL;
}
if (media->app_device) {
device_destroy(g_media_class, MKDEV(EXT_DRV_MEDIA_DEVICE_MAJOR, media->minor));
media->app_device = NULL;
}
if (media->base_device) {
ext_media_device_unregister(media->base_device);
media->base_device = NULL;
}
g_media_minors[i >> 3] &= ~(1 << (i & 7)); /* 3 1 7 表示偏移位数 */
mutex_unlock(&g_media_sem);
return 0;
}
int drv_pm_mod_init(void)
{
int ret;
proc_create("media", 0, NULL, &g_media_proc_fops);
ret = media_bus_init();
if (ret) {
goto err0;
}
g_media_class = class_create(THIS_MODULE, "media_class");
ret = PTR_ERR(g_media_class);
if (IS_ERR(g_media_class)) {
goto err1;
}
ret = -EIO;
if (register_chrdev(EXT_DRV_MEDIA_DEVICE_MAJOR, "media_char_dev", &g_media_fops)) {
goto err2;
}
#if defined(MODULE)
printk("Load soc_media.ko success.\t(%s)\n", VERSION_STRING);
#endif
return 0;
err2:
printk("!!! Module media: unable to get major %d for media devices\n", EXT_DRV_MEDIA_DEVICE_MAJOR);
class_destroy(g_media_class);
err1:
media_bus_exit();
err0:
remove_proc_entry("media", NULL);
return ret;
}
void drv_pm_mod_exit(void)
{
if (list_empty(&g_media_list) == 0) {
return;
}
unregister_chrdev(EXT_DRV_MEDIA_DEVICE_MAJOR, "media");
class_destroy(g_media_class);
media_bus_exit();
remove_proc_entry("media", NULL);
#if defined(MODULE)
printk("remove soc_media.ko success.\n");
#endif
return;
}
EXPORT_SYMBOL(drv_pm_register);
EXPORT_SYMBOL(drv_pm_un_register);
EXPORT_SYMBOL(drv_pm_mod_init);
EXPORT_SYMBOL(drv_pm_mod_exit);
MODULE_LICENSE("GPL");