diff --git a/drivers/net/wwan/mediatek/Makefile b/drivers/net/wwan/mediatek/Makefile
index ae5f8a5ba05a..122a791e1683 100644
--- a/drivers/net/wwan/mediatek/Makefile
+++ b/drivers/net/wwan/mediatek/Makefile
@@ -4,7 +4,8 @@ MODULE_NAME := mtk_tmi
 
 mtk_tmi-y = \
 	pcie/mtk_pci.o	\
-	mtk_dev.o
+	mtk_dev.o	\
+	mtk_bm.o
 
 ccflags-y += -I$(srctree)/$(src)/
 ccflags-y += -I$(srctree)/$(src)/pcie/
diff --git a/drivers/net/wwan/mediatek/mtk_bm.c b/drivers/net/wwan/mediatek/mtk_bm.c
new file mode 100644
index 000000000000..fa5abb82d038
--- /dev/null
+++ b/drivers/net/wwan/mediatek/mtk_bm.c
@@ -0,0 +1,369 @@
+// SPDX-License-Identifier: BSD-3-Clause-Clear
+/*
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#include <linux/list.h>
+#include <linux/netdevice.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include "mtk_bm.h"
+
+#define MTK_RELOAD_TH 3
+#define MTK_WQ_NAME_LEN 48
+
+static int mtk_bm_page_pool_create(struct mtk_bm_pool *pool)
+{
+	INIT_LIST_HEAD(&pool->list.buff_list);
+
+	return 0;
+}
+
+static void mtk_bm_page_pool_destroy(struct mtk_bm_pool *pool)
+{
+	struct mtk_buff *mb, *next;
+
+	list_for_each_entry_safe(mb, next, &pool->list.buff_list, entry) {
+		list_del(&mb->entry);
+		skb_free_frag(mb->data);
+		kmem_cache_free(pool->bm_ctrl->list_cache_pool, mb);
+	}
+}
+
+static void *mtk_bm_page_buff_alloc(struct mtk_bm_pool *pool)
+{
+	struct mtk_buff *mb;
+	void *data;
+
+	spin_lock_bh(&pool->lock);
+	mb = list_first_entry_or_null(&pool->list.buff_list, struct mtk_buff, entry);
+	if (!mb) {
+		spin_unlock_bh(&pool->lock);
+		data = netdev_alloc_frag(pool->buff_size);
+	} else {
+		list_del(&mb->entry);
+		pool->curr_cnt--;
+		spin_unlock_bh(&pool->lock);
+		data = mb->data;
+		kmem_cache_free(pool->bm_ctrl->list_cache_pool, mb);
+	}
+
+	if (pool->curr_cnt < pool->threshold)
+		queue_work(pool->reload_workqueue, &pool->reload_work);
+
+	return data;
+}
+
+static void mtk_bm_page_buff_free(struct mtk_bm_pool *pool, void *data)
+{
+	struct mtk_buff *mb;
+
+	if (pool->curr_cnt >= pool->buff_cnt) {
+		skb_free_frag(data);
+		return;
+	}
+
+	mb = kmem_cache_alloc(pool->bm_ctrl->list_cache_pool, GFP_KERNEL);
+	if (mb) {
+		mb->data = data;
+		spin_lock_bh(&pool->lock);
+		list_add_tail(&mb->entry, &pool->list.buff_list);
+		pool->curr_cnt++;
+		spin_unlock_bh(&pool->lock);
+	} else {
+		skb_free_frag(data);
+	}
+}
+
+static void mtk_bm_page_pool_reload(struct work_struct *work)
+{
+	struct mtk_bm_pool *pool = container_of(work, struct mtk_bm_pool, reload_work);
+	struct mtk_buff *mb;
+
+	while (pool->curr_cnt < pool->buff_cnt && !atomic_read(&pool->work_stop)) {
+		mb = kmem_cache_alloc(pool->bm_ctrl->list_cache_pool, GFP_KERNEL);
+		if (!mb)
+			break;
+
+		mb->data = netdev_alloc_frag(pool->buff_size);
+		if (!mb->data) {
+			kmem_cache_free(pool->bm_ctrl->list_cache_pool, mb);
+			break;
+		}
+
+		spin_lock_bh(&pool->lock);
+		list_add_tail(&mb->entry, &pool->list.buff_list);
+		pool->curr_cnt++;
+		spin_unlock_bh(&pool->lock);
+	}
+}
+
+static struct mtk_buff_ops page_buf_ops = {
+	.pool_create = mtk_bm_page_pool_create,
+	.pool_destroy = mtk_bm_page_pool_destroy,
+	.buff_alloc = mtk_bm_page_buff_alloc,
+	.buff_free = mtk_bm_page_buff_free,
+	.pool_reload = mtk_bm_page_pool_reload,
+};
+
+static int mtk_bm_skb_pool_create(struct mtk_bm_pool *pool)
+{
+	skb_queue_head_init(&pool->list.skb_list);
+
+	return 0;
+}
+
+static void mtk_bm_skb_pool_destroy(struct mtk_bm_pool *pool)
+{
+	skb_queue_purge(&pool->list.skb_list);
+}
+
+static void *mtk_bm_skb_buff_alloc(struct mtk_bm_pool *pool)
+{
+	gfp_t gfp = GFP_KERNEL;
+	struct sk_buff *skb;
+
+	spin_lock_bh(&pool->lock);
+	skb = __skb_dequeue(&pool->list.skb_list);
+	spin_unlock_bh(&pool->lock);
+	if (!skb) {
+		if (in_irq() || in_softirq())
+			gfp = GFP_ATOMIC;
+		skb = __dev_alloc_skb(pool->buff_size, gfp);
+	}
+
+	if (pool->list.skb_list.qlen < pool->threshold)
+		queue_work(pool->reload_workqueue, &pool->reload_work);
+
+	return skb;
+}
+
+static void mtk_bm_skb_buff_free(struct mtk_bm_pool *pool, void *data)
+{
+	struct sk_buff *skb = data;
+
+	if (pool->list.skb_list.qlen < pool->buff_cnt) {
+		/* reset sk_buff (take __alloc_skb as ref.) */
+		skb->data = skb->head;
+		skb->len = 0;
+		skb_reset_tail_pointer(skb);
+		/* reserve memory as netdev_alloc_skb */
+		skb_reserve(skb, NET_SKB_PAD);
+
+		spin_lock_bh(&pool->lock);
+		__skb_queue_tail(&pool->list.skb_list, skb);
+		spin_unlock_bh(&pool->lock);
+	} else {
+		dev_kfree_skb_any(skb);
+	}
+}
+
+static void mtk_bm_skb_pool_reload(struct work_struct *work)
+{
+	struct mtk_bm_pool *pool = container_of(work, struct mtk_bm_pool, reload_work);
+	struct sk_buff *skb;
+
+	while (pool->list.skb_list.qlen < pool->buff_cnt && !atomic_read(&pool->work_stop)) {
+		skb = __dev_alloc_skb(pool->buff_size, GFP_KERNEL);
+		if (!skb)
+			break;
+
+		spin_lock_bh(&pool->lock);
+		__skb_queue_tail(&pool->list.skb_list, skb);
+		spin_unlock_bh(&pool->lock);
+	}
+}
+
+static struct mtk_buff_ops skb_buf_ops = {
+	.pool_create = mtk_bm_skb_pool_create,
+	.pool_destroy = mtk_bm_skb_pool_destroy,
+	.buff_alloc = mtk_bm_skb_buff_alloc,
+	.buff_free = mtk_bm_skb_buff_free,
+	.pool_reload = mtk_bm_skb_pool_reload,
+};
+
+/* mtk_bm_init - Init struct mtk_bm_ctrl
+ *
+ * @mdev: pointer to mtk_md_dev
+ *
+ * Return: return value is 0 on success, a negative error code on failure.
+ */
+int mtk_bm_init(struct mtk_md_dev *mdev)
+{
+	char wq_name[MTK_WQ_NAME_LEN];
+	struct mtk_bm_ctrl *bm;
+
+	bm = devm_kzalloc(mdev->dev, sizeof(*bm), GFP_KERNEL);
+	if (!bm)
+		return -ENOMEM;
+
+	bm->list_cache_pool = kmem_cache_create(mdev->dev_str, sizeof(struct mtk_buff), 0, 0, NULL);
+	if (unlikely(!bm->list_cache_pool))
+		goto err_free_buf;
+
+	snprintf(wq_name, sizeof(wq_name), "mtk_pool_reload_work_h_%s", mdev->dev_str);
+	bm->pool_reload_workqueue_h = alloc_workqueue(wq_name,
+						      WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_HIGHPRI, 0);
+	if (!bm->pool_reload_workqueue_h)
+		goto err_destroy_cache_pool;
+
+	snprintf(wq_name, sizeof(wq_name), "mtk_pool_reload_work_l_%s", mdev->dev_str);
+	bm->pool_reload_workqueue_l = alloc_workqueue(wq_name,
+						      WQ_UNBOUND | WQ_MEM_RECLAIM, 0);
+	if (!bm->pool_reload_workqueue_l)
+		goto err_destroy_wq;
+
+	mutex_init(&bm->pool_list_mtx);
+	INIT_LIST_HEAD(&bm->pool_list);
+
+	bm->m_ops[MTK_BUFF_SKB] = &skb_buf_ops;
+	bm->m_ops[MTK_BUFF_PAGE] = &page_buf_ops;
+	mdev->bm_ctrl = bm;
+
+	return 0;
+
+err_destroy_wq:
+	flush_workqueue(bm->pool_reload_workqueue_h);
+	destroy_workqueue(bm->pool_reload_workqueue_h);
+err_destroy_cache_pool:
+	kmem_cache_destroy(bm->list_cache_pool);
+err_free_buf:
+	devm_kfree(mdev->dev, bm);
+	return -ENOMEM;
+}
+
+/* mtk_bm_pool_create - Create a buffer pool
+ *
+ * @mdev: pointer to mtk_md_dev
+ * @type: pool type
+ * @buff_size: the buffer size
+ * @buff_cnt: the buffer count
+ * @prio: the priority of reload work
+ *
+ * Return: return value is a buffer pool on success, a NULL pointer on failure.
+ */
+struct mtk_bm_pool *mtk_bm_pool_create(struct mtk_md_dev *mdev,
+				       enum mtk_buff_type type, unsigned int buff_size,
+				       unsigned int buff_cnt, unsigned int prio)
+{
+	struct mtk_bm_ctrl *bm = mdev->bm_ctrl;
+	struct mtk_bm_pool *pool;
+
+	pool = devm_kzalloc(mdev->dev, sizeof(*pool), GFP_KERNEL);
+	if (!pool)
+		return NULL;
+
+	pool->type = type;
+	pool->buff_size = buff_size;
+	pool->buff_cnt = buff_cnt;
+	pool->pool_id = bm->pool_seq++;
+	pool->threshold = pool->buff_cnt - pool->buff_cnt / MTK_RELOAD_TH;
+	pool->dev = mdev->dev;
+
+	if (prio == MTK_BM_HIGH_PRIO)
+		pool->reload_workqueue = bm->pool_reload_workqueue_h;
+	else
+		pool->reload_workqueue = bm->pool_reload_workqueue_l;
+	pool->prio = prio;
+
+	spin_lock_init(&pool->lock);
+	pool->ops = bm->m_ops[pool->type];
+	INIT_WORK(&pool->reload_work, pool->ops->pool_reload);
+	if (pool->ops->pool_create(pool))
+		goto err_free_buf;
+	queue_work(pool->reload_workqueue, &pool->reload_work);
+	atomic_set(&pool->work_stop, 0);
+	pool->bm_ctrl = bm;
+
+	mutex_lock(&bm->pool_list_mtx);
+	list_add_tail(&pool->entry, &bm->pool_list);
+	mutex_unlock(&bm->pool_list_mtx);
+
+	return pool;
+
+err_free_buf:
+	dev_err(mdev->dev, "Failed to create bm pool\n");
+	devm_kfree(mdev->dev, pool);
+	return NULL;
+}
+
+/* mtk_bm_alloc - alloc a block of buffer from bm pool
+ *
+ * @pool: the buffer pool
+ *
+ * Return: return value is a block of buffer from bm pool on success, a NULL pointer on failure.
+ */
+void *mtk_bm_alloc(struct mtk_bm_pool *pool)
+{
+	return pool->ops->buff_alloc(pool);
+}
+
+/* mtk_bm_free - free a block of buffer to bm pool
+ *
+ * @pool: the buffer pool
+ * @data: the buffer need to free to pool
+ *
+ * Return: return value is 0 on success, a negative error code on failure.
+ */
+int mtk_bm_free(struct mtk_bm_pool *pool, void *data)
+{
+	if (!data)
+		return -EINVAL;
+
+	pool->ops->buff_free(pool, data);
+
+	return 0;
+}
+
+/* mtk_bm_pool_destroy - destroy a buffer pool
+ * rule: we must stop calling alloc/free before this function is called.
+ *
+ * @mdev: pointer to mtk_md_dev
+ * @pool: the buffer pool need to destroy
+ *
+ * Return: return value is 0 on success, a negative error code on failure.
+ */
+int mtk_bm_pool_destroy(struct mtk_md_dev *mdev, struct mtk_bm_pool *pool)
+{
+	struct mtk_bm_ctrl *bm = mdev->bm_ctrl;
+
+	atomic_set(&pool->work_stop, 1);
+	cancel_work_sync(&pool->reload_work);
+	spin_lock_bh(&pool->lock);
+	pool->curr_cnt = 0;
+	spin_unlock_bh(&pool->lock);
+
+	mutex_lock(&bm->pool_list_mtx);
+	list_del(&pool->entry);
+	mutex_unlock(&bm->pool_list_mtx);
+
+	pool->ops->pool_destroy(pool);
+
+	devm_kfree(mdev->dev, pool);
+	return 0;
+}
+
+/* mtk_bm_exit - deinit struct mtk_bm_ctrl
+ *
+ * @mdev : pointer to mtk_md_dev
+ *
+ * Return: return value is 0 on success, a negative error code on failure.
+ */
+int mtk_bm_exit(struct mtk_md_dev *mdev)
+{
+	struct mtk_bm_ctrl *bm = mdev->bm_ctrl;
+
+	flush_workqueue(bm->pool_reload_workqueue_h);
+	destroy_workqueue(bm->pool_reload_workqueue_h);
+	flush_workqueue(bm->pool_reload_workqueue_l);
+	destroy_workqueue(bm->pool_reload_workqueue_l);
+
+	if (unlikely(!list_empty(&bm->pool_list)))
+		dev_warn(mdev->dev, "bm pool not destroyed\n");
+
+	kmem_cache_destroy(bm->list_cache_pool);
+	devm_kfree(mdev->dev, bm);
+
+	return 0;
+}
diff --git a/drivers/net/wwan/mediatek/mtk_bm.h b/drivers/net/wwan/mediatek/mtk_bm.h
new file mode 100644
index 000000000000..6ac473c05296
--- /dev/null
+++ b/drivers/net/wwan/mediatek/mtk_bm.h
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: BSD-3-Clause-Clear
+ *
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#ifndef __MTK_BM_H__
+#define __MTK_BM_H__
+
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+
+#include "mtk_dev.h"
+
+#define MTK_BM_LOW_PRIO 0
+#define MTK_BM_HIGH_PRIO 1
+
+enum mtk_buff_type {
+	MTK_BUFF_SKB = 0,
+	MTK_BUFF_PAGE,
+	MTK_BUFF_MAX
+};
+
+struct mtk_bm_ctrl {
+	unsigned int pool_seq;
+	struct workqueue_struct *pool_reload_workqueue_h;
+	struct workqueue_struct *pool_reload_workqueue_l;
+	struct kmem_cache *list_cache_pool;
+	struct list_head pool_list;
+	struct mutex pool_list_mtx;	/* protects the pool list */
+	struct mtk_buff_ops *m_ops[MTK_BUFF_MAX];
+};
+
+struct mtk_buff {
+	struct list_head entry;
+	void *data;
+};
+
+union mtk_buff_list {
+	struct sk_buff_head skb_list;
+	struct list_head buff_list;
+};
+
+struct mtk_bm_pool {
+	unsigned int pool_id;
+	enum mtk_buff_type type;
+	unsigned int threshold;
+	unsigned int buff_size;
+	unsigned int buff_cnt;
+	unsigned int curr_cnt;
+	unsigned int prio;
+	atomic_t work_stop;
+	spinlock_t lock;		/* protects the buffer operation */
+	union mtk_buff_list list;
+	struct device *dev;
+	struct work_struct reload_work;
+	struct workqueue_struct *reload_workqueue;
+	struct list_head entry;
+	struct mtk_bm_ctrl *bm_ctrl;
+	struct mtk_buff_ops *ops;
+};
+
+struct mtk_buff_ops {
+	int (*pool_create)(struct mtk_bm_pool *pool);
+	void (*pool_destroy)(struct mtk_bm_pool *pool);
+	void *(*buff_alloc)(struct mtk_bm_pool *pool);
+	void (*buff_free)(struct mtk_bm_pool *pool, void *data);
+	void (*pool_reload)(struct work_struct *work);
+};
+
+int mtk_bm_init(struct mtk_md_dev *mdev);
+int mtk_bm_exit(struct mtk_md_dev *mdev);
+struct mtk_bm_pool *mtk_bm_pool_create(struct mtk_md_dev *mdev,
+				       enum mtk_buff_type type, unsigned int buff_size,
+				       unsigned int buff_cnt, unsigned int prio);
+int mtk_bm_pool_destroy(struct mtk_md_dev *mdev, struct mtk_bm_pool *pool);
+void *mtk_bm_alloc(struct mtk_bm_pool *pool);
+int mtk_bm_free(struct mtk_bm_pool *pool, void *data);
+
+#endif /* __MTK_BM_H__ */
diff --git a/drivers/net/wwan/mediatek/mtk_dev.c b/drivers/net/wwan/mediatek/mtk_dev.c
index d3d7bf940d78..513aac37cb9c 100644
--- a/drivers/net/wwan/mediatek/mtk_dev.c
+++ b/drivers/net/wwan/mediatek/mtk_dev.c
@@ -3,15 +3,24 @@
  * Copyright (c) 2022, MediaTek Inc.
  */
 
+#include "mtk_bm.h"
 #include "mtk_dev.h"
 
 int mtk_dev_init(struct mtk_md_dev *mdev)
 {
-	return 0;
+	int ret;
+
+	ret = mtk_bm_init(mdev);
+	if (ret)
+		goto err_bm_init;
+
+err_bm_init:
+	return ret;
 }
 
 void mtk_dev_exit(struct mtk_md_dev *mdev)
 {
+	mtk_bm_exit(mdev);
 }
 
 int mtk_dev_start(struct mtk_md_dev *mdev)
diff --git a/drivers/net/wwan/mediatek/mtk_dev.h b/drivers/net/wwan/mediatek/mtk_dev.h
index bd7b1dc11daf..0c4b727b9c53 100644
--- a/drivers/net/wwan/mediatek/mtk_dev.h
+++ b/drivers/net/wwan/mediatek/mtk_dev.h
@@ -130,6 +130,7 @@ struct mtk_md_dev {
 	u32 hw_ver;
 	int msi_nvecs;
 	char dev_str[MTK_DEV_STR_LEN];
+	struct mtk_bm_ctrl *bm_ctrl;
 };
 
 int mtk_dev_init(struct mtk_md_dev *mdev);
