GKI için çekirdek kodu geliştirme

Genel Çekirdek Görüntüsü (GKI), yukarı akış Linux çekirdeğiyle yakından uyum sağlayarak çekirdek parçalanmasını azaltır. Ancak bazı yamaların yukarı akışta kabul edilememesinin geçerli nedenleri vardır ve karşılanması gereken ürün planları olduğundan bazı yamalar, GKI'nin oluşturulduğu Android Ortak Çekirdeği (ACK) kaynaklarında tutulur.

Geliştiriciler, ilk tercih olarak Linux Kernel Mailing List (LKML) aracılığıyla kod değişikliklerini yukarı akışa göndermeli ve kod değişikliklerini yalnızca yukarı akışın uygun olmadığına dair güçlü bir neden varsa ACK android-mainline dalına göndermelidir. Geçerli nedenlere ve bunların nasıl ele alınacağına dair örnekler aşağıda listelenmiştir.

  • Yama, LKML'ye gönderildi ancak bir ürün sürümü için zamanında kabul edilmedi. Bu yamayı işlemek için:

    • Yamanın LKML'ye gönderildiğine ve yamayla ilgili yorumlar alındığına veya yamanın yukarı akışa gönderileceği tahmini zamana dair kanıt sunun.
    • Yamanın ACK'ye eklenmesi, yukarı akışta onaylanması ve nihai yukarı akış sürümü ACK'ye birleştirildiğinde ACK'den çıkarılması için bir işlem planı belirleyin.
  • Yama, bir satıcı modülü için EXPORT_SYMBOLS_GPL() tanımlıyor ancak bu sembolü kullanan ağaç içi modül olmadığından yukarı akışa gönderilemedi. Bu yamayı işlemek için modülünüzün neden yukarı akışa gönderilemediği ve bu isteği yapmadan önce düşündüğünüz alternatifler hakkında ayrıntılı bilgi verin.

  • Yama, yukarı akış için yeterince genel değil ve ürün yayınlanmadan önce yeniden düzenlemek için zaman yok. Bu yamayı işlemek için yeniden düzenlenmiş bir yamanın yukarı akışa gönderileceği tahmini süreyi belirtin (yeniden düzenlenmiş bir yamayı inceleme için yukarı akışa gönderme planı olmadan yama ACK'de kabul edilmez).

  • Yama, yukarı akış tarafından kabul edilemiyor çünkü... <insert reason here>. Bu yamayı işlemek için Android çekirdek ekibiyle iletişime geçin ve yamayı incelemeye gönderilebilecek ve yukarı akışta kabul edilebilecek şekilde yeniden düzenleme seçenekleri konusunda bizimle birlikte çalışın.

Daha birçok olası gerekçe vardır. Hatanızı veya yamanızı gönderirken geçerli bir gerekçe ekleyin. Bazı yinelemeler ve tartışmalar olabilir. ACK'nin, özellikle herkesin yukarı akışta nasıl çalışılacağını öğrendiği ancak bunu yapmak için ürün planlarını rahatlatamadığı GKI'nın ilk aşamalarında bazı yamalar taşıdığını biliyoruz. Yukarı akış gereksinimlerinin zaman içinde daha katı hale gelmesini bekliyoruz.

Yama gereksinimleri

Yama gönderimleri, ister yukarı akışa ister ACK'ye gönderilsin Linux kaynak ağacında açıklanan Linux çekirdeği kodlama standartlarına uygun olmalıdır. scripts/checkpatch.pl Komut dosyası, Gerrit gönderme öncesi testinin bir parçası olarak çalıştırılır. Bu nedenle, başarılı olduğundan emin olmak için önceden uygulayın. checkpatch komut dosyasını, gönderme öncesi testle aynı yapılandırmayla çalıştırmak için //build/kernel/static_analysis:checkpatch_presubmit kullanın. Ayrıntılı bilgi için build/kernel/kleaf/docs/checkpatch.md adresini ziyaret edin.

Onay yamaları

ACK'ye gönderilen yamalar, Linux çekirdeği kodlama standartlarına ve katkı yönergelerine uygun olmalıdır. Commit iletisine bir Change-Id etiketi eklemeniz gerekir. Yama birden fazla dala (örneğin, android-mainline ve android12-5.4) gönderiyorsanız yamanın tüm örnekleri için aynı Change-Id etiketini kullanmanız gerekir.

Yama gönderirken önce LKML'ye göndererek üst akış incelemesi yapın. Yama:

  • Yukarı akışta kabul edilen değişiklikler, android-mainline'ya otomatik olarak birleştirilir.
  • Yukarı akışta kabul edilmedi. Yukarı akış gönderimine referans vererek veya LKML'ye neden gönderilmediğini açıklayarak android-mainline adresine gönderin.

Bir yama, yukarı akışta veya android-mainline'da kabul edildikten sonra uygun LTS tabanlı ACK'ye (ör. Android'e özgü kodu düzelten yamalar için android12-5.4 ve android11-5.4) geri aktarılabilir. android-mainline'ye gönderme işlemi, yeni üst akış yayın adaylarıyla test yapılmasına olanak tanır ve yamanın bir sonraki LTS tabanlı ACK'de olmasını sağlar. Bir yukarı akış yamasının android12-5.4'ya geri taşındığı durumlar (yama büyük olasılıkla zaten android-mainline'dadır) istisnadır.

Yukarı akış yamaları

Katkı yönergelerinde belirtildiği gibi, ACK çekirdeklerine yönelik yukarı akış yamaları aşağıdaki gruplara ayrılır (kabul edilme olasılığına göre sıralanmıştır).

  • UPSTREAM: - "android-mainline"dan seçilen yamalar, makul bir kullanım alanı varsa ACK'ye kabul edilebilir.
  • BACKPORT: - Temiz bir şekilde seçilmeyen ve değiştirilmesi gereken yukarı akış yamaları, makul bir kullanım alanı varsa da kabul edilebilir.
  • FROMGIT: - Linux ana hattına göndermeye hazırlanırken bir bakımcı dalından seçilen yamalar, yaklaşan bir son tarih varsa kabul edilebilir. Bunlar hem içerik hem de program açısından gerekçelendirilmelidir.
  • FROMLIST: - LKML'ye gönderilen ancak henüz bir bakımcı dalına kabul edilmemiş yamaların, gerekçesi yeterince ikna edici olmadığı sürece kabul edilmesi olası değildir. Bu durumda yama, yukarı akış Linux'a dahil edilse de edilmese de kabul edilir (edilmeyeceğini varsayıyoruz). Android çekirdek ekibiyle görüşmeyi kolaylaştırmak için FROMLIST yamalarıyla ilişkili bir sorun olmalıdır.

Android'e özel yamalar

Gerekli değişiklikleri yukarı akışta yapamıyorsanız ACK'ye doğrudan ağaç dışı yamalar göndermeyi deneyebilirsiniz. Ağaç dışı yamaların gönderilmesi için BT'de yama ve nedenini belirten bir sorun oluşturmanız gerekir. Yama, yukarı akışa neden gönderilemez? (Örnekler için önceki listeye bakın). Ancak kodun yukarı akışa gönderilemediği birkaç durum vardır. Bu durumlar aşağıdaki şekilde ele alınır ve Android'e özel yamalar için katkı yönergelerine uyulması, ayrıca konunun ANDROID: önekiyle etiketlenmesi gerekir.

gki_defconfig dosyasında yapılan değişiklikler

CONFIG mimariye özgü olmadığı sürece gki_defconfig ile ilgili tüm CONFIG değişiklikleri hem arm64 hem de x86 sürümlerine uygulanmalıdır. CONFIG ayarında değişiklik isteğinde bulunmak için BT'de bir sorun oluşturarak değişikliği görüşün. Dondurulduktan sonra çekirdek modülü arayüzünü (KMI) etkileyen tüm değişiklikler reddedilir.CONFIG İş ortaklarının tek bir yapılandırma için çakışan ayarlar talep ettiği durumlarda, ilgili hatalar hakkındaki tartışmalar aracılığıyla çakışmaları gideririz.

Yukarı akışta bulunmayan kod

Zaten Android'e özgü olan kodda yapılan değişiklikler yukarı akışa gönderilemez. Örneğin, bağlayıcı sürücüsü yukarı akışta korunsa da bağlayıcı sürücüsünün öncelik devralma özelliklerinde yapılan değişiklikler Android'e özgü oldukları için yukarı akışa gönderilemez. Hatanızı ve kodun neden yukarı akışa gönderilemediğini açıkça belirtin. Mümkünse yamaları, yukarı akışa gönderilebilen parçalar ve yukarı akışa gönderilemeyen Android'e özgü parçalar olarak ayırın. Böylece ACK'de tutulan ağaç dışı kod miktarı en aza indirilir.

Bu kategorideki diğer değişiklikler; KMI gösterim dosyaları, KMI sembol listeleri, gki_defconfig, derleme komut dosyaları veya yapılandırması ya da yukarı akışta bulunmayan diğer komut dosyalarıyla ilgili güncellemelerdir.

Ağaç dışı modüller

Yukarı akış Linux, ağaç dışı modüller oluşturma desteğini aktif olarak engeller. Linux bakımcılarının çekirdek içi kaynak veya ikili uyumluluk konusunda garanti vermemesi ve ağaçta olmayan kodu desteklemek istememesi nedeniyle bu makul bir yaklaşımdır. Ancak GKI, tedarikçi modülleri için ABI garantileri verir ve KMI arayüzlerinin, desteklenen çekirdek ömrü boyunca kararlı olmasını sağlar. Bu nedenle, ACK için kabul edilebilir ancak yukarı akış için kabul edilemez olan tedarikçi modüllerini desteklemek üzere bir değişiklik sınıfı vardır.

Örneğin, dışa aktarma işlemini kullanan modüllerin kaynak ağacında olmadığı durumlarda EXPORT_SYMBOL_GPL() makroları ekleyen bir yamayı ele alalım. EXPORT_SYMBOL_GPL() için yukarı akış isteğinde bulunmaya ve yeni dışa aktarılan sembolü kullanan bir modül sağlamaya çalışmanız gerekir. Ancak modülün neden yukarı akışa gönderilmediğine dair geçerli bir gerekçe varsa yama yerine ACK'yi gönderebilirsiniz. Modülün neden yukarı akışa alınamayacağına dair gerekçeyi soruna eklemeniz gerekir. (GPL olmayan varyantı EXPORT_SYMBOL() istemeyin.)

Gizli yapılandırmalar

Bazı ağaç içi modüller, gki_defconfig içinde belirtilemeyen gizli yapılandırmaları otomatik olarak seçer. Örneğin, CONFIG_SND_SOC_SOF=y yapılandırıldığında CONFIG_SND_SOC_TOPOLOGY otomatik olarak seçilir. GKI, ağaç dışı modül oluşturmayı desteklemek için gizli yapılandırmaları etkinleştirmeye yönelik bir mekanizma içerir.

Gizli bir yapılandırmayı etkinleştirmek için select ifadesini init/Kconfig.gki içine ekleyin. Böylece, gki_defconfig içinde etkinleştirilen CONFIG_GKI_HACKS_TO_FIX çekirdek yapılandırmasına göre otomatik olarak seçilir. Bu mekanizmayı yalnızca gizli yapılandırmalar için kullanın; yapılandırma gizli değilse gki_defconfig içinde açıkça veya bağımlılık olarak belirtilmelidir.

Yüklenebilir valiler

Yüklenebilir yöneticileri destekleyen çekirdek çerçevelerinde (ör. cpufreq) varsayılan yöneticinin (ör. cpufreq'nın schedutil yöneticisi) yerine geçebilirsiniz. Yüklenebilir yöneticileri veya sürücüleri desteklemeyen ancak yine de tedarikçiye özel bir uygulama gerektiren çerçeveler (ör. termal çerçeve) için BT'de bir sorun oluşturun ve Android çekirdek ekibine danışın.

Gerekli desteği eklemek için sizinle ve üst akış bakımcılarıyla birlikte çalışacağız.

Tedarikçi kancaları

Geçmiş sürümlerde, tedarikçiye özel değişiklikleri doğrudan çekirdeğe ekleyebiliyordunuz. Ürüne özel kodun modüllerde uygulanması gerektiği ve yukarı akışta çekirdek çekirdeklerde veya ACK'de kabul edilmeyeceği için GKI 2.0 ile bu mümkün değildir. GKI, iş ortaklarının kullandığı ve temel çekirdek kodu üzerinde minimum etkiye sahip olan katma değerli özellikleri etkinleştirmek için modüllerin temel çekirdek kodundan çağrılmasına olanak tanıyan satıcı kancalarını kabul eder. Ayrıca, bu özellikleri uygulamak için satıcıya özgü verileri depolamaya yönelik satıcı veri alanlarıyla önemli veri yapıları doldurulabilir.

Tedarikçi kancaları, tedarikçi modüllerinin ekleyebileceği izleme noktalarına (izleme etkinliklerine değil) dayalı olarak iki varyantta (normal ve kısıtlanmış) sunulur. Örneğin, görevden çıkışta muhasebe yapmak için yeni bir sched_exit() işlevi eklemek yerine, tedarikçiler do_exit()'ye bir kanca ekleyebilir. Bu kancaya, işleme için bir tedarikçi modülü bağlanabilir. Örnek bir uygulamada aşağıdaki satıcı kancaları bulunur.

  • Normal tedarikçi kancaları, DECLARE_HOOK() kullanarak trace_name adlı bir izleme noktası işlevi oluşturur. Burada name, izlemenin benzersiz tanımlayıcısıdır. Normal tedarikçi kancası adları geleneksel olarak android_vh ile başlar. Bu nedenle, sched_exit() kancasının adı android_vh_sched_exit olur.
  • CPU çevrimdışı olsa bile veya atomik olmayan bir bağlam gerektirse bile eklenen işlevin çağrılması gereken zamanlayıcı kancaları gibi durumlarda kısıtlanmış tedarikçi kancaları gerekir. Kısıtlanmış sağlayıcı kancaları ayrılamaz. Bu nedenle, kısıtlanmış bir kancaya bağlanan modüller hiçbir zaman kaldırılamaz. Kısıtlanmış tedarikçi kancası adları android_rvh ile başlar.

Tedarikçi kancası eklemek için BT'de bir sorun kaydı oluşturun ve yamaları gönderin (tüm Android'e özgü yamalarda olduğu gibi, bir sorun olmalı ve gerekçe sunmalısınız). Satıcı kancaları yalnızca ACK'de desteklenir. Bu nedenle, bu yamaları yukarı akış Linux'a göndermeyin.

Yapılara tedarikçi alanları ekleme

ANDROID_VENDOR_DATA() makrolarını kullanarak android_vendor_data alanları ekleyerek tedarikçi verilerini önemli veri yapılarıyla ilişkilendirebilirsiniz. Örneğin, katma değerli özellikleri desteklemek için aşağıdaki kod örneğinde gösterildiği gibi alanları yapılara ekleyin.

Tedarikçilerin ihtiyaç duyduğu alanlar ile OEM'lerin ihtiyaç duyduğu alanlar arasında olası çakışmaları önlemek için OEM'ler, ANDROID_VENDOR_DATA() makroları kullanılarak tanımlanan alanları asla kullanmamalıdır. Bunun yerine, OEM'ler android_oem_data alanlarını bildirmek için ANDROID_OEM_DATA() kullanmalıdır.

#include <linux/android_vendor.h>
...
struct important_kernel_data {
  [all the standard fields];
  /* Create vendor data for use by hook implementations. The
   * size of vendor data is based on vendor input. Vendor data
   * can be defined as single u64 fields like the following that
   * declares a single u64 field named "android_vendor_data1" :
   */
  ANDROID_VENDOR_DATA(1);

  /*
   * ...or an array can be declared. The following is equivalent to
   * u64 android_vendor_data2[20]:
   */
  ANDROID_VENDOR_DATA_ARRAY(2, 20);

  /*
   * SoC vendors must not use fields declared for OEMs and
   * OEMs must not use fields declared for SoC vendors.
   */
  ANDROID_OEM_DATA(1);

  /* no further fields */
}

Tedarikçi kancalarını tanımlama

DECLARE_HOOK() veya DECLARE_RESTRICTED_HOOK() kullanarak bildirdikten sonra izleme noktası olarak koda ekleyerek çekirdek koduna satıcı kancaları ekleyin. Örneğin, mevcut do_exit() çekirdek işlevine trace_android_vh_sched_exit() eklemek için:

#include <trace/hooks/exit.h>
void do_exit(long code)
{
    struct task_struct *tsk = current;
    ...
    trace_android_vh_sched_exit(tsk);
    ...
}

trace_android_vh_sched_exit() işlevi, başlangıçta yalnızca bir şeyin eklenip eklenmediğini kontrol eder. Ancak bir satıcı modülü register_trace_android_vh_sched_exit() kullanarak bir işleyici kaydederse kayıtlı işlev çağrılır. İşleyici, tutulan kilitler, RCS durumu ve diğer faktörlerle ilgili bağlamın farkında olmalıdır. Kanca, include/trace/hooks dizinindeki bir üstbilgi dosyasında tanımlanmalıdır.

Örneğin, aşağıdaki kod, include/trace/hooks/exit.h dosyasındaki trace_android_vh_sched_exit() için olası bir bildirim verir.

/* SPDX-License-Identifier: GPL-2.0 */
#undef TRACE_SYSTEM
#define TRACE_SYSTEM sched
#define TRACE_INCLUDE_PATH trace/hooks

#if !defined(_TRACE_HOOK_SCHED_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_HOOK_SCHED_H
#include <trace/hooks/vendor_hooks.h>
/*
 * Following tracepoints are not exported in tracefs and provide a
 * mechanism for vendor modules to hook and extend functionality
 */

struct task_struct;

DECLARE_HOOK(android_vh_sched_exit,
             TP_PROTO(struct task_struct *p),
             TP_ARGS(p));

#endif /* _TRACE_HOOK_SCHED_H */

/* This part must be outside protection */
#include <trace/define_trace.h>

Tedarikçi kancası için gereken arayüzleri örneklendirmek üzere kanca bildirimi içeren üst dosyasını drivers/android/vendor_hooks.c öğesine ekleyin ve sembolleri dışa aktarın. Örneğin, aşağıdaki kod, android_vh_sched_exit() kancasının bildirimini tamamlar.

#ifndef __GENKSYMS__
/* struct task_struct */
#include <linux/sched.h>
#endif

#define CREATE_TRACE_POINTS
#include <trace/hooks/vendor_hooks.h>
#include <trace/hooks/exit.h>
/*
 * Export tracepoints that act as a bare tracehook (i.e. have no trace
 * event associated with them) to allow external modules to probe
 * them.
 */
EXPORT_TRACEPOINT_SYMBOL_GPL(android_vh_sched_exit);

NOT: Kanca bildiriminde kullanılan veri yapılarının, ABI kararlılığını garanti etmek için tam olarak tanımlanması gerekir. Aksi takdirde, opak işaretçilerin referansını kaldırmak veya yapıyı boyutlandırılmış bağlamlarda kullanmak güvenli değildir. Bu tür veri yapılarının tam tanımını sağlayan include, drivers/android/vendor_hooks.c'nin #ifndef __GENKSYMS__ bölümünün içine gitmelidir. include/trace/hooks içindeki üst dosyalar, KMI'yi bozan CRC değişikliklerini önlemek için tür tanımlarını içeren çekirdek başlık dosyasını içermemelidir. Bunun yerine türleri iletin bildirin.

Tedarikçi kancalarına ekleme

Tedarikçi kancalarını kullanmak için tedarikçi modülünün kanca için bir işleyici kaydetmesi gerekir (genellikle modül başlatma sırasında yapılır). Örneğin, aşağıdaki kodda foo.ko için trace_android_vh_sched_exit() modülü işleyicisi gösterilmektedir.

#include <trace/hooks/sched.h>
...
static void foo_sched_exit_handler(void *data, struct task_struct *p)
{
    foo_do_exit_accounting(p);
}
...
static int foo_probe(..)
{
    ...
    rc = register_trace_android_vh_sched_exit(foo_sched_exit_handler, NULL);
    ...
}

Üstbilgi dosyalarındaki satıcı kancalarını kullanma

Üst dosyalarındaki satıcı kancalarını kullanmak için satıcı kancası üst dosyasını güncellemeniz gerekebilir. Bu işlem, izleme noktası üst dosyasının bulunamadığını belirten derleme hatalarını önlemek için TRACE_INCLUDE_PATH öğesinin tanımını kaldırır. Örneğin,

In file included from .../common/init/main.c:111:
In file included from .../common/include/trace/events/initcall.h:74:
.../common/include/trace/define_trace.h:95:10: fatal error: 'trace/hooks/initcall.h' file not found
   95 | #include TRACE_INCLUDE(TRACE_INCLUDE_FILE)
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.../common/include/trace/define_trace.h:90:32: note: expanded from macro 'TRACE_INCLUDE'
   90 | # define TRACE_INCLUDE(system) __TRACE_INCLUDE(system)
      |                                ^~~~~~~~~~~~~~~~~~~~~~~
.../common/include/trace/define_trace.h:87:34: note: expanded from macro '__TRACE_INCLUDE'
   87 | # define __TRACE_INCLUDE(system) __stringify(TRACE_INCLUDE_PATH/system.h)
      |                                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.../common/include/linux/stringify.h:10:27: note: expanded from macro '__stringify'
   10 | #define __stringify(x...)       __stringify_1(x)
      |                                 ^~~~~~~~~~~~~~~~
.../common/include/linux/stringify.h:9:29: note: expanded from macro '__stringify_1'
    9 | #define __stringify_1(x...)     #x
      |                                 ^~
<scratch space>:14:1: note: expanded from here
   14 | "trace/hooks/initcall.h"
      | ^~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.

Bu tür bir derleme hatasını düzeltmek için, dahil ettiğiniz tedarikçi kancası üstbilgi dosyasına eşdeğer düzeltmeyi uygulayın. Daha fazla bilgi için https://r.android.com/3066703 adresini ziyaret edin.

diff --git a/include/trace/hooks/mm.h b/include/trace/hooks/mm.h
index bc6de7e53d66..039926f7701d 100644
--- a/include/trace/hooks/mm.h
+++ b/include/trace/hooks/mm.h
@@ -2,7 +2,10 @@
 #undef TRACE_SYSTEM
 #define TRACE_SYSTEM mm

+#ifdef CREATE_TRACE_POINTS
 #define TRACE_INCLUDE_PATH trace/hooks
+#define UNDEF_TRACE_INCLUDE_PATH
+#endif

UNDEF_TRACE_INCLUDE_PATH tanımlandığında include/trace/define_trace.h, izleme noktaları oluşturulduktan sonra TRACE_INCLUDE_PATH öğesinin tanımını kaldırması için talimat verilir.

Temel çekirdek özellikleri

Önceki tekniklerin hiçbiri bir modüldeki özelliği uygulamanıza olanak tanımıyorsa özelliği çekirdek çekirdeğinde Android'e özgü bir değişiklik olarak eklemeniz gerekir. Konuşmayı başlatmak için sorun izleyicide (BT) bir sorun oluşturun.

Kullanıcı uygulama programlama arayüzü (UAPI)

  • UAPI üstbilgi dosyaları. Değişiklikler Android'e özgü arayüzlerde yapılmadığı sürece UAPI üstbilgi dosyalarında yapılan değişiklikler yukarı akışta gerçekleşmelidir. Tedarikçi modülleri ile tedarikçi kullanıcı alanı kodu arasındaki arayüzleri tanımlamak için tedarikçiye özel başlık dosyalarını kullanın.
  • sysfs düğümleri. GKI çekirdeğine yeni sysfs düğümleri eklemeyin (bu tür eklemeler yalnızca satıcı modüllerinde geçerlidir). Android çerçevesini oluşturan SoC ve cihazdan bağımsız kitaplıklar ile Java kodu tarafından kullanılan sysfs düğümleri yalnızca uyumlu şekilde değiştirilebilir ve Android'e özgü sysfs düğümleri değilse yukarı akışta değiştirilmelidir. Tedarikçi kullanıcı alanında kullanılacak tedarikçiye özel sysfs düğümleri oluşturabilirsiniz. Varsayılan olarak, SELinux kullanılarak kullanıcı alanının sysfs düğümlerine erişimi reddedilir. Yetkili satıcı yazılımının erişmesine izin vermek için uygun SELinux etiketlerini eklemek satıcının sorumluluğundadır.
  • DebugFS düğümleri. Tedarikçi modülleri, yalnızca hata ayıklama için debugfs içinde düğümler tanımlayabilir (çünkü debugfs, cihazın normal çalışması sırasında monte edilmez).