summaryrefslogtreecommitdiffstats
path: root/data/lineageos_wiki/find_lineageos_devices.py
blob: 9fe0bd670032881312d14b790acd04226c4b5f87 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
#!/bin/env python
# -*- coding: utf-8 -*-
# Program to find devices to support in LineageOS
# Copyright (C) 2019 Denis 'GNUtoo' Carikli <GNUtoo@cyberdimension.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

import copy
import os
import re
import sys
import yaml

basedir = '_data' + os.sep + 'devices'
results = {}
# Reference: https://en.wikipedia.org/wiki/LineageOS#Version_history
last_lineageos_version = 21

def get_devices(document):
    if type(document['soc']) == str:
        return [document]
    # find7_variant{1,2}.yml and Z00{A,B}.yml have 2 SOCs (and
    # variants) so we need to treat them as 2 devices.
    elif type(document['soc']) == list:
        devices = []
        for soc in document['soc']:
            device = copy.copy(document)
            for key in ['codename', 'name']:
                if key in document.keys():
                    device[key] = document[key] + ' (' + soc + ')'
            if 'soc' in document.keys():
                    device['soc'] = soc
            devices.append(device)
        return devices
    else:
        assert(False)

def protocol_supported(document, protocol):
    if protocol in document['network']:
        return True
    else:
        return False

def still_supported(device):
    if not last_lineageos_version in device['versions']:
        return False
    if 'channels' in device.keys() and 'discontinued' in device['channels']:
       return False
    if 'maintainers' in device.keys() and len(device['maintainers']) == 0:
        return False

    return True

# In some cases it might be interesting to look if a given device has a modem in
# the case where the SOC is known not to have any modem, so we can automatically
# know that the is no shared memory between the modem and the device if there is
# no modem.
# Another case would be for SOCs that have optional modems where the device has
# no modem feature and that we are sure that the modem CPU is not running any code.
def device_has_modem(vendor, product):
    if vendor == 'Google' and product == 'Pixel C':
        return False

    # unknown
    return None

def device_has_shared_memory_between_modem_and_soc(vendor, product):
    if vendor == 'Samsung' and product == 'Galaxy S5 Neo':
        # In the "Samsung Mobile Modem Driver (SVNET2) V1 for
        # Memory-type Interface" section in lineageos_s5neolte_defconfig
        # in the lineage-17.0 branch of
        # https://github.com/LineageOS/android_kernel_samsung_universal7580
        # there is the following configuration:
        # CONFIG_LINK_DEVICE_SHMEM=y
        # CONFIG_LINK_DEVICE_HSIC is not set
        return True
    if vendor == 'Samsung' and product == 'Galaxy J7 (2015)':
        # lineageos_j7elte_defconfig in the lineage-17.1 branch of
        # android_kernel_samsung_universal7580 has the following:
        # CONFIG_LINK_DEVICE_SHMEM=y. All other CONFIG_LINK_DEVICE_
        # are 'is not set'.
        return True

    # unknown
    return None

def soc_has_modem(vendor, product):
    if vendor == 'Amlogic' and product in ['S905x2', 'S905y2']:
        # Reference:
        # https://en.wikipedia.org/wiki/Amlogic#Media_player_SoCs_(S9_family_gen_2)
        # doesn't says that S905y2 or S905x2 have modems and they are
        # "Media player" SOCs anyway.
        return False
    elif vendor == 'Amlogic' and product in ['S905y3']:
        # While at the time of writing, this SOC is not present in
        # https://en.wikipedia.org/wiki/Amlogic#Media_player_SoCs_(S9_family_gen_2)
        # , given the SOC namings, it's likely to also be a media
        # player SOC and so to not contain a modem.
        return False
    elif vendor == 'Google' and product in ['Tensor GS101',
                                            'Tensor GS201',
                                            'Tensor G3']:
        # Reference:
        # https://en.wikipedia.org/wiki/Google_Tensor#Models says that
        # the GS101 and GS201 have an Exynos modem inside.
        return True
    elif vendor == 'HiSilicon':
        pass
    elif vendor == 'Intel':
        pass
    elif vendor == 'Mediatek' and product in ['MT6768 Helio G85', 'MT6785 Helio G95']:
        # Reference: https://en.wikipedia.org/wiki/List_of_MediaTek_systems_on_chips#Helio_G_Series_(2019%E2%80%93present)
        return True
    elif vendor in ['Nvidia', 'NVIDIA'] and re.search("^Tegra 4", product):
        # The modem could be implemented in software
        pass
    elif vendor in ['Nvidia', 'NVIDIA'] and re.search("^Tegra K1", product):
        pass
    elif vendor in ['Nvidia', 'NVIDIA'] and re.search("^Tegra X1", product):
        pass
    elif vendor in ['Nvidia', 'NVIDIA'] and re.search("^Tegra X2", product):
        pass
    elif vendor == 'Qualcomm' and re.search("^MSM", product):
        return True
    elif vendor == 'Qualcomm' and re.search("^APQ", product) or re.search("^Snapdragon 600", product):
        return False
    elif vendor == 'Qualcomm' and product == "Qualcomm SDM845 Snapdragon 845":
        return True
    elif vendor == 'Qualcomm' and re.search("^SDM", product):
        pass
    elif vendor == 'Qualcomm' and (re.search("^SM", product) or re.search("^sm", product)):
        pass
    elif vendor == 'Qualcomm' and re.search("^Snapdragon", product):
        pass
    elif vendor == 'Samsung' and re.search("^Exynos", product):
        # Some exynos modems do exists but they are either standalone
        # modems or are SOCs where the AP is a Cortex M7 which are very
        # unlikely to be able to run Android for smartphones or tablets
        return False
    elif vendor == 'TI' and product in ['OMAP4430', 'OMAP4460']:
        # The modem could be implemented in software in the DSP but I never saw
        # it on any device. It's used like that on the SysmoBTS which are BTS,
        # not phones or a tablets.
        return False
    else:
        print("<{}|{}>".format(vendor, product))
        sys.exit(1)
    # Unknown
    return None

def parse_soc(soc):
    soc_vendors = [
        "Amlogic",
        "Google",
        "HiSilicon",
        "Intel",
        "Mediatek",
        "NVIDIA",
        "Nvidia",
        "Qualcomm",
        "Samsung",
        "TI"]

    soc_vendors_quirks = {
        # ^Match : Vendor
        'Exynos' : 'Samsung',
    }

    results = {}
    found = False
    for vendor in soc_vendors:
        match = re.search("^{0} (.*)".format(vendor), soc)
        if match:
            found = True
            if vendor == 'NVIDIA':
                vendor = 'Nvidia'
            results['vendor'] = vendor
            results['product'] = match.group(1)
            results['has_modem'] = soc_has_modem(results['vendor'], results['product'])

    if not found:
        for match_data, vendor in soc_vendors_quirks.items():
            match = re.search("^{0} (.*)".format(match_data), soc)
            if match:
                found = True
                results['vendor'] = vendor
                results['product'] = soc
                results['has_modem'] = soc_has_modem(results['vendor'], results['product'])

    if not 'vendor' in results:
        print("TODO: Handle vendor in \"{}\"".format(soc))
        sys.exit(1)

    return results

def battery_is_removable(battery):
    # Example: Set top box
    if battery == "None" or battery == None:
        return None

    if 'removable' not in battery:
        print("TODO: Handle batteries where removable is absent: {}".format(battery))
        sys.exit(1)

    removable_battery = battery.get('removable')
    if removable_battery == True:
        return True
    elif removable_battery == False:
        return False
    else:
        print("TODO: Handle batteries where removable is \"{}\": {}".format(removable, battery))
        sys.exit(1)

def has_removable_battery(device):
    removable_battery = None
    if 'battery' not in device:
        # We don't know the policies reguarding incomplete data so the data
        # may either be incomplete or the device may not have a battery.
        print("TODO: Add support for devices lacking a battery")
        sys.exit(1)
    else:
        battery = device.get('battery')
        if type(battery) is not list:
            return battery_is_removable(battery)
        else:
            for battery_version in battery:
                if battery_version == None:
                    continue

                if len(battery_version.keys()) != 1:
                    print("TODO: Add support for battery versions with multiple keys: {}".format(battery_version))
                    sys.exit(1)

                nr_removable = 0
                nr_not_removable = 0

                (battery_name, battery_data), = battery_version.items()

                removable = battery_is_removable(battery_data)
                if removable == True:
                    nr_removable += 1
                elif removable == False:
                    nr_not_removable += 1

            if nr_removable == 0 and nr_not_removable > 0:
                return False
            elif nr_not_removable == 0 and nr_removable > 0:
                return True
            else:
                print("TODO: The data has removable and non-removable batteries"
                      "versions for the same device: {}".format(battery))
                print("      Add support for it in the parsing code")
                sys.exit(1)

def interesting_for_replicant(device):
    # For smartphones or tablets with a modem, since the modem is in
    # the same SOC, we would need to do some extensive review of the SOC
    # architecture to understand if the modem can be isolated with an IOMMU
    # We would also need to make sure that the IOMMU is correctly setup (hard)
    # and that the setup happens before the modem core are booted.
    # For devices without a modem, we would still need to make sure that the
    # sound card and the microphone is completely under the control of free
    # software and that there aren't too much proprietary libraries to replace
    soc = parse_soc(device['soc'])
    if soc_has_modem(soc['vendor'], soc['product']) == True:
        return False

    if device_has_shared_memory_between_modem_and_soc(device['vendor'],
                                                      device['name']):
        return False

    # The 2G and 3G procol families are being removed. Given how long
    # it takes to add support for a device, it's not worth investing
    # too much time in supporting devices without support for these
    # protocols. Note that 4G LTE isn't telling if voice will work on
    # 4G networks as this requires VoLTE.
    if not protocol_supported(device, '4G LTE') and \
       not protocol_supported(device, '5G NR'):
        return False

    # Non replaceable batteries causes too much issues for both users
    # and developers as once you buy a device second hand, the battery
    # doesn't last as much as when the device is new. On some devices,
    # replicing non-removable batteries can be very difficult and damage
    # the device along the way
    #
    # There are some devices where the battery is not removable but
    # since the device can be easily opened, and that the battery has
    # a connector, it might be possible for an experienced user or
    # developer, or a repair shop, or a repair café to open the device
    # and replace the battery.
    #
    # However it would take more research to understand if compatible
    # batteries replacement can easily be found. The LineageOS wiki also
    # doesn't have a way to distinguish between devices that can be easily
    # opened and the ones that aren't.
    #
    # So for now we aproximate non-removable batteries to non-replaceable
    # batteries until we find a way to deal with it.
    if device['type'] != 'Set top box' and not has_removable_battery(device):
        return False

    return True

def store_infos(results, device):
    fields = ['vendor', 'name', 'type']

    device_dict = {}
    for field in fields:
        device_dict[field] = device[field]

    device_dict['removable_battery'] = has_removable_battery(device)

    soc = device['soc']
    soc_data = parse_soc(soc)
    soc_vendor = None
    soc_product = None

    if soc_data['vendor'] not in results:
        results[soc_data['vendor']] = {}
    soc_vendor = results[soc_data['vendor']]

    if soc_data['product'] not in soc_vendor:
        soc_vendor[soc_data['product']] = []
    soc_product = soc_vendor[soc_data['product']]

    soc_product.append(device_dict)

def print_results(results):
    soc_vendors = list(results.keys())
    soc_vendors.sort()
    for soc_vendor in soc_vendors:
        print ("{0}:".format(soc_vendor))
        for soc_product in results[soc_vendor]:
            print ("- {0}:".format(soc_product))
            for device in results[soc_vendor][soc_product]:
                print("  * {0}: {1} ({2})".format(
                    device['vendor'], device['name'], device['type']))

def find_devices(path, device_types):
    for filename in os.listdir(path + os.sep + basedir):
        filepath = path + os.sep + basedir + os.sep + filename
        if re.search("\.yml$", filepath):
            yaml_file = open(filepath, 'r')
            document = yaml.safe_load(yaml_file)
            for device in get_devices(document):
                if device_types == 'replicant':
                    if still_supported(device) and \
                       interesting_for_replicant(device):
                        store_infos(results, device)
                elif device_types == 'all':
                    store_infos(results, device)

    print_results(results)

def usage(argv0):
    progname = os.path.basename(argv0)
    print("{} [path/to/lineage_wiki] [all|replicant]".format(progname))
    sys.exit(1)

if len(sys.argv) not in [2, 3]:
    usage(sys.argv[0])
elif len(sys.argv) == 2:
    find_devices(sys.argv[1], 'replicant')
elif len(sys.argv) == 3:
    find_devices(sys.argv[1], sys.argv[2])