00 QMI protokol mimarisi — Qualcomm Modem Interface
QMI (Qualcomm MSM Interface), Qualcomm MDM yongasetlerinde kullanılan tescilli bir protokoldür. AT komutlarına göre çok daha hızlı ve zengin API sunar.
QMI Protokol Yığını
──────────────────────────────────────────────────
Uygulama (qmicli, libqmi, Python)
↓ libqmi userspace kütüphanesi
QMI servisler:
├── WDS (Wireless Data Service) → data oturumları
├── NAS (Network Access Service) → ağ durumu, sinyal
├── DMS (Device Management Svc) → cihaz bilgisi
├── UIM (User Identity Module) → SIM yönetimi
├── WMS (Wireless Messaging) → SMS
├── LOC (Location Service) → GPS/GNSS
└── PDC (Platform Device Config) → firmware
↓ /dev/cdc-wdm0 (cdc_wdm kernel driver)
USB cihaz → Qualcomm MDM modem
↓ USB vendor-specific interface
Modem RF yığını (EC25, RM500Q, Sierra EM7455...)
QMI vs AT karşılaştırması
# USB cihazı gör
lsusb
# Bus 001 Device 003: ID 2c7c:0125 Quectel EC25
# qmi_wwan driver yüklü mü?
lsmod | grep qmi
# qmi_wwan 28672 0
# cdc_wdm 20480 1 qmi_wwan
# Driver yükle (gerekirse)
modprobe qmi_wwan
modprobe cdc_wdm
# QMI kontrol cihazı
ls /dev/cdc-wdm*
# /dev/cdc-wdm0
# wwan0 ağ arayüzü
ip link show wwan0
# 4: wwan0: <BROADCAST,MULTICAST,NOARP> mtu 1500 ...
# USB interface numarası ile eşleşme
ls -la /sys/class/net/wwan0/device/
cat /sys/class/net/wwan0/device/uevent
# DRIVER=qmi_wwan
# PRODUCT=2c7c/125/0
01 libqmi kurulum ve qmicli temel kullanımı
libqmi, QMI protokolünü implement eden açık kaynak kütüphanedir. qmicli, libqmi'yi kullanan komut satırı aracıdır.
# Debian / Ubuntu
apt-get install libqmi-utils
# Versiyon kontrol
qmicli --version
# qmicli 1.34.0
# Cihaz bilgisi al (DMS)
qmicli -d /dev/cdc-wdm0 --dms-get-ids
# [/dev/cdc-wdm0] Device IDs retrieved:
# ESN: '0'
# IMEI: '867729040000001'
# MEID: '0'
# Üretici bilgisi
qmicli -d /dev/cdc-wdm0 --dms-get-manufacturer
# [/dev/cdc-wdm0] Device manufacturer retrieved:
# Manufacturer: 'Quectel'
# Model ve revision
qmicli -d /dev/cdc-wdm0 --dms-get-model
qmicli -d /dev/cdc-wdm0 --dms-get-revision
# QMI modem state
qmicli -d /dev/cdc-wdm0 --dms-get-operating-mode
# [/dev/cdc-wdm0] Operating mode retrieved:
# Mode: 'online'
# Raw IP modu ayarla (wwan0 için gerekli)
qmicli -d /dev/cdc-wdm0 --wda-set-data-format=raw-ip
ip link set wwan0 arp off
ip link set wwan0 up
02 WDS servisi — data session yönetimi
WDS (Wireless Data Service), QMI'nin veri oturumlarını yöneten servisidir. Bağlantı kurma, kesme ve durum sorgulama burada gerçekleşir.
# WDS paket servisi durumu sorgula
qmicli -d /dev/cdc-wdm0 --wds-get-packet-service-status
# [/dev/cdc-wdm0] Packet service status retrieved:
# Connection status: 'disconnected'
# Data bağlantısı kur (APN ile)
qmicli -d /dev/cdc-wdm0 \
--wds-start-network="apn=internet,ip-type=4" \
--client-no-release-cid
# [/dev/cdc-wdm0] Network started
# Packet data handle: '2249109920'
# [/dev/cdc-wdm0] Client ID not released:
# Service: 'wds'
# CID: '19'
# ÖNEMLİ: handle ve CID değerlerini kaydet
HANDLE=2249109920
CID=19
# Bağlantı durumunu sorgula
qmicli -d /dev/cdc-wdm0 --wds-get-packet-service-status
# Connection status: 'connected'
# IP ayarlarını al
qmicli -d /dev/cdc-wdm0 --wds-get-current-settings
# [/dev/cdc-wdm0] Current settings retrieved:
# IP Family: IPv4
# IP Address: 100.65.45.123
# Prefix: 30
# Gateway: 100.65.45.124
# Primary DNS: 8.8.8.8
# Secondary DNS: 8.8.4.4
# IP adresini wwan0'a ata
ip addr add 100.65.45.123/30 dev wwan0
ip route add default via 100.65.45.124 dev wwan0
echo "nameserver 8.8.8.8" > /etc/resolv.conf
# Bağlantıyı kes
qmicli -d /dev/cdc-wdm0 \
--wds-stop-network=$HANDLE \
--client-cid=$CID
# Veri istatistikleri
qmicli -d /dev/cdc-wdm0 --wds-get-data-bearer-technology
qmicli -d /dev/cdc-wdm0 --wds-get-packet-statistics
Tam bağlantı script'i
#!/bin/bash
# QMI ile tam LTE bağlantı scripti
DEVICE=/dev/cdc-wdm0
IFACE=wwan0
APN="${1:-internet}"
log() { logger -t qmi-connect "$*"; echo "$*"; }
# Raw IP modu
log "Raw IP modu ayarlanıyor..."
qmicli -d $DEVICE --wda-set-data-format=raw-ip
ip link set $IFACE arp off
ip link set $IFACE up
# Data session başlat
log "Data session başlatılıyor (APN=$APN)..."
OUTPUT=$(qmicli -d $DEVICE \
--wds-start-network="apn=$APN,ip-type=4" \
--client-no-release-cid 2>&1)
HANDLE=$(echo "$OUTPUT" | grep -oP 'handle: '\''\K[0-9]+')
CID=$(echo "$OUTPUT" | grep -oP 'CID: '\''\K[0-9]+')
if [ -z "$HANDLE" ]; then
log "HATA: Bağlantı kurulamadı"
echo "$OUTPUT"
exit 1
fi
log "Bağlantı kuruldu. Handle=$HANDLE, CID=$CID"
# IP ayarlarını al ve uygula
SETTINGS=$(qmicli -d $DEVICE --wds-get-current-settings 2>&1)
IP=$(echo "$SETTINGS" | grep -oP 'IP Address: \K[\d.]+')
GW=$(echo "$SETTINGS" | grep -oP 'Gateway: \K[\d.]+')
DNS=$(echo "$SETTINGS" | grep -oP 'Primary DNS: \K[\d.]+')
log "IP=$IP GW=$GW DNS=$DNS"
ip addr flush dev $IFACE
ip addr add "$IP/30" dev $IFACE
ip route add default via "$GW" dev $IFACE metric 100
echo "nameserver $DNS" > /etc/resolv.conf
# Handle ve CID'yi kaydet (disconnect için)
echo "$HANDLE $CID" > /run/qmi-session
log "LTE bağlantısı hazır: $IP"
03 NAS servisi — network durumu ve sinyal
NAS (Network Access Service), ağ kayıt durumu, operatör seçimi ve sinyal ölçümlerini sağlar. AT+CREG ve AT+CSQ'nun QMI karşılığıdır.
# Sinyal gücü ölç
qmicli -d /dev/cdc-wdm0 --nas-get-signal-strength
# [/dev/cdc-wdm0] Successfully got signal strength
# Current:
# Network 'lte': '-71 dBm'
# RSSI:
# Network 'lte': '-71 dBm'
# ECIO:
# Network 'lte': '0.0 dBm'
# SNR:
# Network 'lte': '22.8 dB'
# IO: '-106 dBm'
# Sinyal detayları (RSRP, RSRQ)
qmicli -d /dev/cdc-wdm0 --nas-get-signal-info
# [/dev/cdc-wdm0] Successfully got signal info
# LTE:
# RSSI: '-71 dBm'
# RSRQ: '-11 dB'
# RSRP: '-93 dBm'
# SNR: '22.8 dB'
# Network kayıt durumu
qmicli -d /dev/cdc-wdm0 --nas-get-serving-system
# [/dev/cdc-wdm0] Got serving system:
# Registration state: 'registered'
# CS: 'attached' PS: 'attached'
# MCC: '286' MNC: '01' (Turkcell)
# Roaming: no
# LTE cell bilgisi
qmicli -d /dev/cdc-wdm0 --nas-get-cell-location-info
# [/dev/cdc-wdm0] Got cell location info
# Intrafrequency LTE Info:
# UE In Idle: 'no'
# PLMN: '28601'
# Tracking Area Code: '0x0B13'
# Global Cell ID: '0x021A4D01'
# EUTRA Absolute RF Channel Number: 1750
# Serving Cell ID: 123
# Cell Reselection Priority: 4
# Non-intrafrequency threshold: -10
# Threshold serving low: -10
# Desteklenen bandlar
qmicli -d /dev/cdc-wdm0 --nas-get-rf-band-info
# [/dev/cdc-wdm0] Got RF band info
# Radio Interface: 'lte'
# Band: 'eutran-3'
# Active Bandwidth: '20'
# Operatör tarama
qmicli -d /dev/cdc-wdm0 --nas-network-scan
# (bu işlem uzun sürebilir, ~30s)
04 DMS servisi — cihaz bilgisi ve firmware
DMS (Device Management Service), modem kimliği, donanım bilgisi, pil durumu ve firmware güncelleme işlemlerini kapsar.
# Tüm cihaz bilgisi
qmicli -d /dev/cdc-wdm0 --dms-get-ids
qmicli -d /dev/cdc-wdm0 --dms-get-manufacturer
qmicli -d /dev/cdc-wdm0 --dms-get-model
qmicli -d /dev/cdc-wdm0 --dms-get-revision
# Donanım versiyonu
qmicli -d /dev/cdc-wdm0 --dms-get-hardware-revision
# [/dev/cdc-wdm0] Hardware revision retrieved:
# Revision: '10000'
# Çalışma modu
qmicli -d /dev/cdc-wdm0 --dms-get-operating-mode
# Mode: 'online'
# HW restricted: 'no'
# Çevrimdışı moda geç (RF kapat)
qmicli -d /dev/cdc-wdm0 --dms-set-operating-mode=offline
# Online moda dön
qmicli -d /dev/cdc-wdm0 --dms-set-operating-mode=online
# Modem sıfırla
qmicli -d /dev/cdc-wdm0 --dms-set-operating-mode=reset
# Desteklenen bandlar
qmicli -d /dev/cdc-wdm0 --dms-get-band-capabilities
# UIM (SIM) slot bilgisi
qmicli -d /dev/cdc-wdm0 --uim-get-card-status
# [/dev/cdc-wdm0] Successfully got card status
# Slot [1]:
# Card state: 'present'
# UPIN state: 'unblocked'
# UPIN retries: '3'
# UPUK retries: '10'
# Application [1]:
# Type: 'usim'
# State: 'ready'
# Personalization state: 'ready'
# UPIN replaces PIN1: 'no'
# PIN1 state: 'disabled'
05 MBIM protokolü — Microsoft USB modem standardı
MBIM (Mobile Broadband Interface Model), USB IF tarafından standartlaştırılmış modem protokolüdür. Qualcomm, Sierra, Telit ve diğer üreticiler destekler.
MBIM Protokol Yığını
──────────────────────────────────────────────────
Uygulama (mbimcli, libmbim, NetworkManager)
↓ libmbim userspace kütüphanesi
MBIM komutlar:
├── MBIM_CID_DEVICE_CAPS → cihaz bilgisi
├── MBIM_CID_SUBSCRIBER_READY → SIM durumu
├── MBIM_CID_REGISTER_STATE → ağ kaydı
├── MBIM_CID_SIGNAL_STATE → sinyal gücü
├── MBIM_CID_CONNECT → data bağlantısı
├── MBIM_CID_IP_CONFIGURATION → IP ayarları
└── MBIM_CID_SMS_* → SMS
↓ /dev/cdc-wdm0 (cdc_mbim kernel driver)
USB CDC NCM/MBIM arayüzü
↓
Modem (Sierra EM7455, Telit FN980, Quectel EM120R)
# cdc_mbim driver yüklü mü?
lsmod | grep mbim
# cdc_mbim 28672 0
# cdc_wdm 20480 1 cdc_mbim
# MBIM kontrol cihazı
ls /dev/cdc-wdm*
# /dev/cdc-wdm0
# wwan0 ağ arayüzü (MBIM için de aynı)
ip link show wwan0
# QMI mi MBIM mi? USB descriptor'a bak
lsusb -v -d 2c7c:0125 | grep -A3 "bInterfaceSubClass"
# bInterfaceSubClass 14 = MBIM
# bInterfaceSubClass 255 = QMI (vendor specific)
06 libmbim ve mbimcli kullanımı
libmbim ve mbimcli, MBIM modemlerini yönetmek için kullanılır. qmicli'ye benzer sözdizimi sunar.
# Kurulum
apt-get install libmbim-utils
# Versiyon
mbimcli --version
# mbimcli 1.28.0
# Cihaz bilgisi
mbimcli -d /dev/cdc-wdm0 --query-device-caps
# [/dev/cdc-wdm0] Device capabilities retrieved:
# Device type: 'embedded'
# Cellular class: 'gsm'
# Voice class: 'no-voice'
# SIM class: 'removable'
# Data class: 'lte'
# SMS caps: 'pdu-send, pdu-receive'
# Control caps: 'reg-manual'
# Max sessions: '4'
# Custom data class: ''
# Dev ID: '867729040000001'
# Firmware info: 'EC25EFAR06A12M4G'
# Hardware info: ''
# SIM durumu
mbimcli -d /dev/cdc-wdm0 --query-subscriber-ready-status
# Ready state: 'initialized'
# IMSI: '286011234567890'
# ICCID: '89901012345678901234'
# Subscriber ID: '286011234567890'
# Ağ kayıt durumu
mbimcli -d /dev/cdc-wdm0 --query-register-state
# Register state: 'home'
# Network error: 'unknown'
# Available data class: 'lte'
# Current cellular class: 'gsm'
# Provider ID: '28601'
# Provider name: 'Turkcell'
# Roaming text: ''
# Sinyal gücü
mbimcli -d /dev/cdc-wdm0 --query-signal-state
# Signal: '71' (0-100 arası, 100 en iyi)
# Data bağlantısı kur
mbimcli -d /dev/cdc-wdm0 \
--connect="apn='internet',ip-type=ipv4" \
--no-open --no-close
# [/dev/cdc-wdm0] Successfully connected
# Session ID: '0'
# Activation state: 'activated'
# IP type: 'ipv4'
# IP konfigürasyonu al
mbimcli -d /dev/cdc-wdm0 --query-ip-configuration=0
# IPv4 configuration:
# Method: 'static'
# Address: '100.65.45.123'
# Prefix: '30'
# Gateway: '100.65.45.124'
# DNS: '8.8.8.8'
# Bağlantıyı kes
mbimcli -d /dev/cdc-wdm0 --disconnect=0
07 Raw data session — wwan0 üzerinden trafik
QMI veya MBIM ile data session kurduktan sonra wwan0 arayüzünü ağa hazır hale getirme — raw IP modu ve routing.
# 1. Raw IP modu (QMI için zorunlu)
qmicli -d /dev/cdc-wdm0 --wda-set-data-format=raw-ip
ip link set wwan0 arp off
ip link set wwan0 mtu 1500
ip link set wwan0 up
# 2. Data session başlat ve IP al
qmicli -d /dev/cdc-wdm0 \
--wds-start-network="apn=internet,ip-type=4" \
--client-no-release-cid
# Packet data handle: '2249109920'
# 3. IP ayarlarını uygula
IP="100.65.45.123"
GW="100.65.45.124"
ip addr add ${IP}/30 dev wwan0
ip route add default via $GW dev wwan0 metric 100
# 4. DNS ayarla
echo "nameserver 8.8.8.8" > /etc/resolv.conf
# 5. Bağlantı testi
ping -c 3 -I wwan0 8.8.8.8
# PING 8.8.8.8 (8.8.8.8): 56 data bytes
# 64 bytes from 8.8.8.8: icmp_seq=0 ttl=56 time=28.4 ms
# 6. İstatistikler
ip -s link show wwan0
cat /sys/class/net/wwan0/statistics/rx_bytes
cat /sys/class/net/wwan0/statistics/tx_bytes
# 7. DHCP ile otomatik IP (udhcpc)
# Bazı modemler DHCP'yi de destekler
udhcpc -i wwan0 -n -q -t 10
# 8. Bağlantıyı kes
qmicli -d /dev/cdc-wdm0 \
--wds-stop-network=2249109920 \
--client-cid=19
ip addr flush dev wwan0
ip link set wwan0 down
QMI data session'ı raw IP kullanır — Ethernet frame yok, ARP yok. ip link set wwan0 arp off zorunludur. MBIM bazı durumlarda NCM (Ethernet frame) kullanabilir, bu durumda ARP gerekir.
08 Practical — Python libqmi binding ile modem yönetimi
qmicli'yi subprocess ile çağırarak Python'da modem yönetimi — JSON benzeri ayrıştırma ve tam data session yönetimi.
#!/usr/bin/env python3
"""
QMI modem yöneticisi — qmicli subprocess wrapper.
Gereksinimler: libqmi-utils, Python 3.8+
"""
import subprocess
import re
import time
import logging
import ipaddress
log = logging.getLogger('QMIManager')
logging.basicConfig(level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s')
class QMIManager:
def __init__(self, device='/dev/cdc-wdm0', iface='wwan0'):
self.device = device
self.iface = iface
self._session_handle = None
self._client_cid = None
def _run(self, *args, timeout=30):
cmd = ['qmicli', '-d', self.device] + list(args)
try:
r = subprocess.run(cmd, capture_output=True, text=True,
timeout=timeout)
return r.stdout + r.stderr
except subprocess.TimeoutExpired:
log.error(f"Zaman aşımı: {' '.join(args)}")
return ''
def _parse_field(self, text, pattern):
m = re.search(pattern, text)
return m.group(1).strip() if m else None
# ── Cihaz bilgisi ─────────────────────────────────────
def get_device_info(self):
out = self._run('--dms-get-ids')
out += self._run('--dms-get-manufacturer')
out += self._run('--dms-get-model')
out += self._run('--dms-get-revision')
return {
'imei': self._parse_field(out, r"IMEI: '([^']+)'"),
'manufacturer': self._parse_field(out, r"Manufacturer: '([^']+)'"),
'model': self._parse_field(out, r"Model: '([^']+)'"),
'revision': self._parse_field(out, r"Revision: '([^']+)'"),
}
# ── Sinyal ────────────────────────────────────────────
def get_signal(self):
out = self._run('--nas-get-signal-info')
return {
'rssi': self._parse_field(out, r"RSSI: '(-?\d+ dBm)'"),
'rsrq': self._parse_field(out, r"RSRQ: '(-?\d+ dB)'"),
'rsrp': self._parse_field(out, r"RSRP: '(-?\d+ dBm)'"),
'sinr': self._parse_field(out, r"SNR: '(-?[\d.]+ dB)'"),
}
# ── Ağ kayıt ──────────────────────────────────────────
def get_registration(self):
out = self._run('--nas-get-serving-system')
state = self._parse_field(out, r"Registration state: '([^']+)'")
plmn = self._parse_field(out, r"MNC: '(\d+)'")
return {
'state': state,
'registered': state == 'registered',
'roaming': 'roaming' in (out.lower()),
'plmn': plmn,
}
def wait_registered(self, timeout=120):
log.info("LTE ağına kayıt bekleniyor...")
start = time.time()
while time.time() - start < timeout:
reg = self.get_registration()
if reg['registered']:
log.info(f"Kayıt OK {'(roaming)' if reg['roaming'] else '(ev ağı)'}")
return True
time.sleep(4)
return False
# ── Data session ──────────────────────────────────────
def setup_raw_ip(self):
log.info("Raw IP modu ayarlanıyor...")
self._run('--wda-set-data-format=raw-ip')
subprocess.run(['ip', 'link', 'set', self.iface, 'arp', 'off'])
subprocess.run(['ip', 'link', 'set', self.iface, 'mtu', '1500'])
subprocess.run(['ip', 'link', 'set', self.iface, 'up'])
def start_data_session(self, apn='internet'):
log.info(f"Data session başlatılıyor (APN={apn})...")
out = self._run(
f'--wds-start-network=apn={apn},ip-type=4',
'--client-no-release-cid',
timeout=60
)
self._session_handle = self._parse_field(
out, r"Packet data handle: '(\d+)'"
)
self._client_cid = self._parse_field(
out, r"CID: '(\d+)'"
)
if self._session_handle:
log.info(f"Session: handle={self._session_handle}, CID={self._client_cid}")
return True
log.error(f"Session başlatılamadı: {out}")
return False
def get_ip_settings(self):
out = self._run('--wds-get-current-settings')
return {
'ip': self._parse_field(out, r"IP Address: ([\d.]+)"),
'gw': self._parse_field(out, r"Gateway: ([\d.]+)"),
'dns': self._parse_field(out, r"Primary DNS: ([\d.]+)"),
'prefix': self._parse_field(out, r"Prefix: (\d+)"),
}
def apply_ip_settings(self, settings):
ip, gw, dns = settings['ip'], settings['gw'], settings['dns']
prefix = settings.get('prefix', '30')
log.info(f"IP uygulanıyor: {ip}/{prefix} gw={gw}")
subprocess.run(['ip', 'addr', 'flush', 'dev', self.iface])
subprocess.run(['ip', 'addr', 'add', f'{ip}/{prefix}', 'dev', self.iface])
subprocess.run(['ip', 'route', 'add', 'default', 'via', gw,
'dev', self.iface, 'metric', '100'])
with open('/etc/resolv.conf', 'w') as f:
f.write(f'nameserver {dns}\n')
def stop_data_session(self):
if not self._session_handle:
return
log.info("Data session durduruluyor...")
self._run(
f'--wds-stop-network={self._session_handle}',
f'--client-cid={self._client_cid}'
)
subprocess.run(['ip', 'addr', 'flush', 'dev', self.iface])
subprocess.run(['ip', 'link', 'set', self.iface, 'down'])
self._session_handle = None
self._client_cid = None
# ── Tam bağlantı ──────────────────────────────────────
def connect(self, apn='internet'):
info = self.get_device_info()
log.info(f"Modem: {info.get('manufacturer')} {info.get('model')}")
log.info(f"IMEI : {info.get('imei')}")
self.setup_raw_ip()
if not self.wait_registered():
log.error("Ağ kaydı başarısız")
return False
if not self.start_data_session(apn):
return False
time.sleep(2)
settings = self.get_ip_settings()
if settings['ip']:
self.apply_ip_settings(settings)
log.info(f"Bağlantı hazır: {settings['ip']}")
return True
return False
def main():
qmi = QMIManager(device='/dev/cdc-wdm0', iface='wwan0')
if qmi.connect(apn='internet'):
sig = qmi.get_signal()
log.info(f"Sinyal: {sig}")
# 60 saniye bekle, sonra kes
log.info("60s aktif, sonra bağlantı kesilecek...")
time.sleep(60)
qmi.stop_data_session()
else:
log.error("Bağlantı kurulamadı")
if __name__ == '__main__':
main()
Doğrudan libqmi Python binding için python3-qmi paketi bazı dağıtımlarda mevcuttur. GObject Introspection üzerinden çalışır. subprocess wrapper yaklaşımı ise hiçbir ek bağımlılık gerektirmez ve production'da daha güvenilirdir.